I'm trying to authenticate and check permission of a user while establishing a websocket connection in a NestJS application.
I've found this discussion which recommends to make use of NestJS Websocket adapter. You can perform the token validation in the options.allowRequest callback as below.
export class AuthenticatedSocketIoAdapter extends IoAdapter {
private readonly authService: AuthService;
constructor(private app: INestApplicationContext) {
super(app);
this.authService = app.get(AuthService);
}
createIOServer(port: number, options?: SocketIO.ServerOptions): any {
options.allowRequest = async (request, allowFunction) => {
const token = request.headers.authorization.replace('Bearer ', '');
const verified = this.authService.verifyToken(token);
if (verified) {
return allowFunction(null, true);
}
return allowFunction('Unauthorized', false);
};
return super.createIOServer(port, options);
}
}
I have a problem however with the dependency injection in the websocket adapter. The IoAdapter's constructor has an INestApplicationContext parameter from which I'm trying to get back the AuthService using app.get(AuthService) as you can see above.
The AuthService injects two other services, a UserService and the JwtService to check the JWT token. My problem is that those services remained not defined in that context.
#Injectable()
export class AuthService {
constructor(private usersService: UsersService, private jwtService: JwtService) {}
verifyToken(token: string): boolean {
// Problem: this.jwtService is undefined
const user = this.jwtService.verify(token, { publicKey });
// ... check user has permissions and return result
}
For info, the AuthService is in another module than the one which defines the Websocket. I also tried to import the AuthService (and its dependencies) in the current module but that didn't help.
Is that possible to make use the service using the app.get() method?
I could solve the DI issue by using app.resolve() instead of app.get()
export class AuthenticatedSocketIoAdapter extends IoAdapter {
private authService: AuthService;
constructor(private app: INestApplicationContext) {
super(app);
app.resolve<AuthService>(AuthService).then((authService) => {
this.authService = authService;
});
}
}
This solved the jwtService injected in the AuthService being undefined.
Related
I am new to Sping Boot, rest api and angular12,
I am running my program in vscode to call the back api and i get the error "Failed to load resource: the server responded with a status of 404 (Not Found)"
my codes:
backend controller :
#RestController
#RequestMapping("/evaluationController/")
public class EvaluationController {
#Autowired
private EvaluationRepository evaluationrepository;
//get all evaluationsnotes
#CrossOrigin(origins = "http://localhost:4200/")
#GetMapping("/notes")
public List<EvaluationModel> getAllEvaluations(){
return evaluationrepository.findAll();
}
//post notes
#PostMapping("/notes")
public EvaluationModel createEvaluationNote(#RequestBody EvaluationModel evaluationNote) {
return evaluationrepository.save(evaluationNote);
}
}
My front end angular12 service
#Injectable({
providedIn: 'root'
})
export class EvaluationserviceService {
private baseUrl!: "http://localhost:8080/evaluationController/notes";
constructor(private httpClient: HttpClient) { }
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
}
getEvaluationNotes():Observable<Evaluationotes[]>{
return this.httpClient.get<Evaluationotes[]>(`${this.baseUrl}`);
}
}
my typescript file
#Component({
selector: 'app-fin-evaluation',
templateUrl: './fin-evaluation.component.html',
styleUrls: ['./fin-evaluation.component.css']
})
export class FinEvaluationComponent implements OnInit {
evaluationNote!: Evaluationotes[];
constructor(private evaluationNoteService: EvaluationserviceService ) { }
ngOnInit(): void {
this.getAllNotes();
}
private getAllNotes(){
this.evaluationNoteService.getEvaluationNotes().subscribe(data=>{
this.evaluationNote = data;
});
}
}
Thank you!
The issue is with the baseUrl, you need to use = (used for initialization) instead of : (used in typescript to define an objects type). Since you are never really initializing the variable with proper url, request is going to some random url like http://localhost:4200/undefined causing 404. You can update the url as follows and try:
private baseUrl = "http://localhost:8080/evaluationController/notes";
I am building a microservice project, with two of the services are:
API Gateway: for routing to suitable service.
Authentication service: for authenticating user credentials/user's tokens.
The Authentication service is built by NestJS with TCP protocol.
Forcase login:
HTTP request from client to API Gateway. API Gateway send to Authentication service const oAuthenticatedUser: LoginUserResponseDto = await this.authMicroServiceClient.send('login', loginRequest).toPromise() for validating the email and password. It will return UserInfo (name, access_token and regresh_token) if user's credentials are correct.
For case: create-post
HTTP request from client to API Gateway with access_token in header. Before calling to post-service for executing post-creation, API Gateway call Authentication service for verifying the token by const authenReponse = await this.authMicroServiceClient.send('verify_access_token', { token: access_token }).toPromise();
My pain point: I cannot use Passport strategy in Authentication service for implementing common verifying token. Because the request to Authentication Service is now not a normal HTTP request. Then these current code are not able to use:
import { PassportStrategy } from '#nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { TJwtPayload } from '../../types/auth/jwt-payload.type';
import { UserSimpleDTO } from '#modules/user/dto';
import { ConfigService } from '#nestjs/config'
import { TokenService } from '#app/shared/services'
/**
* Powered Thuan
* #author thuan.nguyen
* #namespace auth
* #classname JwtAccessTokenStrategy
**/
#Injectable()
export class JwtAccessTokenStrategy extends PassportStrategy(Strategy, 'jwt-access-token') {
constructor(private readonly tokenService: TokenService, configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromExtractors([
ExtractJwt.fromAuthHeaderAsBearerToken(),
]),
ignoreExpiration: true,
secretOrKey: configService.get("JWT_ACCESS_SECRET"),
});
}
async validate(payload: TJwtPayload): Promise<UserSimpleDTO> {
const user = await this.tokenService.validatePayload(payload);
if (!user) {
throw new UnauthorizedException('Cannot find user via payload');
}
return user;
}
}
And
import { Strategy } from 'passport-local';
import { PassportStrategy } from '#nestjs/passport';
import { Injectable, UnauthorizedException } from '#nestjs/common';
import { UserService } from '../user/user.service';
import { UserLoginReqDTO } from '#modules/user/dto';
/**
* Powered by Thuan
* #author thuan.nguyen
* #namespace auth
* #classname LocalStrategy
**/
#Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
private userService: UserService,
) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<UserLoginReqDTO> {
const user: UserLoginReqDTO = await this.userService.getUserIfPasswordMatches(email, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
My question is: My design is a good one or not. I would like to know the better design. And How to use above code with requests transfered by TCP protocol.
Thank you!
I have a REST API, I want to send event to the client via websocket.
How to inject websocket instance in controller or another component?
Better solution is to create global module. You can then emit events from any other module/controller. A. Afir approach will create multiple instances of Gateway if you try to use it in other modules.
Note: This is just simplest solution
Create socket.module.ts
import { Module, Global } from '#nestjs/common';
import { SocketService } from './socket.service';
#Global()
#Module({
controllers: [],
providers: [SocketService],
exports: [SocketService],
})
export class SocketModule {}
socket.service.ts
import { Injectable } from '#nestjs/common';
import { Server } from 'socket.io';
#Injectable()
export class SocketService {
public socket: Server = null;
}
app.gateway.ts see afterInit function
import { WebSocketGateway, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, WebSocketServer } from '#nestjs/websockets';
import { Logger } from '#nestjs/common';
import { Server, Socket } from 'socket.io';
import { SocketService } from './socket/socket.service';
#WebSocketGateway()
export class AppGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
constructor(private socketService: SocketService){
}
#WebSocketServer() public server: Server;
private logger: Logger = new Logger('AppGateway');
afterInit(server: Server) {
this.socketService.socket = server;
}
handleDisconnect(client: Socket) {
this.logger.log(`Client disconnected: ${client.id}`);
}
handleConnection(client: Socket, ...args: any[]) {
this.logger.log(`Client connected: ${client.id}`);
}
}
Then import SocketModule into AppModule and you can use Socket service everywhere.
class Gateway can be injected in another component, and use the server instance.
#Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly messageGateway: MessageGateway
) {}
#Get()
async getHello() {
this.messageGateway.server.emit('messages', 'Hello from REST API');
return this.appService.getHello();
}
}
I suppose that #Raold missed a fact in the documentation:
Gateways should not use request-scoped providers because they must act as singletons. Each gateway encapsulates a real socket and cannot be instantiated multiple times.
So it means that we can neither instantiate the gateway class multiple times nor do it explicitly using injection scopes features.
So creating just only one gateway for one namespaces will be right and it will produce only one instance of the websocket or socket.io server.
I am recieving the above error in the aurelia view model
Inner Error: Message: controllerFactory.createForCurrentScope is not a
function
Here is the code,
export class UpdateClient {
public httpClient: HttpClient;
public router: Router;
public clientHelper: ClientHelper;
public validator: Validator;
public canSave: boolean;
public controller: ValidationController;
public client: Client ;
constructor(httpClient: HttpClient, router: Router, clientValidator: ClientValidator, clientHelper: ClientHelper, controllerFactory: ValidationControllerFactory, validator: Validator) {
this.httpClient = httpClient;
this.clientHelper = clientHelper;
this.router = router;
this.client = new Client
this.controller = controllerFactory.createForCurrentScope(validator);
clientValidator.validate(this.client, clientHelper);
}
You need to have #autoinject() or any other decorator on your UpdateClient class for the type metadata to be emitted correctly.
ValidationControllerFactory is registered with DI as a resolver in the module file, so simply importing that (which you have, otherwise you'd get a type error) should give you the correct thing.
You may need to double check that you have experimentalDecorators and emitDecoratorMetadata set to true in your tsconfig.json.
Also make sure you are registering the validation plugin in your main.ts like so:
aurelia.use.plugin(PLATFORM.moduleName('aurelia-validation'))
I have a problem with HttpClient in Angular 5. HttpClient does not send any request (I don't see any xhr log in console) on two specified components. On the others components everything is fine.
Calling ApiService POST method (custom service which works like a wrapper for HttpClient) from Component A, but when I call this method from Component B
HttpClient seems to be frozen.
There are many components in my app that use ApiService. Everything is injected fine. I have no idea what is wrong.
--- respond
ApiService.ts
#Injectable()
export class ApiService
{
private errorListeners : Map<string, Array<(details ?: any) => any>> =
new Map<string, Array<(details ?: any) => any>>();
public constructor(private http: HttpClient)
{
}
public post<T>(path : string, data : any, urlParams : any = null) : Observable<any>
{
return this.http.post<T>(`${environment.api.path}${path}`, data, {
params: urlParams
}).catch(this.catchErrors()).map(response => {
if (response['Error']){
throw response['Error'];
}
return response;
});
}
}
--
Component
#Component({
selector: 'login-register-component',
templateUrl: './register.component.html',
styleUrls: [
'./../../assets/main/css/pages/login.css'
]
})
export class RegisterComponent implements OnInit, OnDestroy
{
public constructor(private route: ActivatedRoute,
private router: Router,
private userService : UserService,
private apiService: ApiService
)
{
this.apiService.post('/some-endpoint', null, {}).subscribe(res => {
console.log(res);
});
}
HttpClient does not work even if i directly inject HttpClient into Component
-- Other component in the same module
example call: (it works)
public loginTraditionalMethod(emailAddress : string, plainPassword : string)
{
this.apiService.post('/auth/email', {
email: emailAddress,
password: plainPassword
}, {}).subscribe(res => {
console.log(res);
})
}
I was having the same problem, no xhr request after subscribing to a http.get(). This was a request for a forgotten password function, I was therefore not connected to the app.
The request was being intercepted by an http token interceptor that was returning an empty Observable if no session was detected.
Never know, this might help someone...