// jwt guard
#UseGuards(AuthStrategyGuard)
#Controller('users')
export class UsersController {
constructor(private readonly authService: AuthService, private readonly usersService: UsersService) {}
#Post()
async getIndex (#Body() body) {
if (!body.user) {
throw new UnauthorizedException('error auth');
} else {
const token = await this.authService.sign(body);
return { token }
}
}
#Post('auth')
validating (#Body('token') token) {
console.log(token, 'token');
return {success: 1}
}
#Post('test')
getTestIndex () {
return {success: 1}
}
}
Here is the JSONWebToken validation logic
I wanted to exclude the #post () decorator from the JWT validation
what should I do?
You can do that by applying the guard only on the methods (endpoints ) you need to validate, in your case, it'll be: ( I assume you want to exclude the validation from the first route
#Controller('users')
export class UsersController {
constructor(private readonly authService: AuthService, private readonly usersService: UsersService) {}
#Post()
async getIndex (#Body() body) {
if (!body.user) {
throw new UnauthorizedException('error auth');
} else {
const token = await this.authService.sign(body);
return { token }
}
}
#UseGuards(AuthStrategyGuard)
#Post('auth')
validating (#Body('token') token) {
console.log(token, 'token');
return {success: 1}
}
#UseGuards(AuthStrategyGuard)
#Post('test')
getTestIndex () {
return {success: 1}
}
}
Related
How to format the response when I got successful response?
For example my code is
#Get(':id')
async getOne(#Param('id') id: string) {
const model = await this.categoriesService.getById(id);
if (model) {
return model;
} else {
throw new NotFoundException('Category not found');
}
}
I got a response is:
{
"id": 1,
"name": "test",
"image": null
}
How to default format to?
{ status: 200|201..., data: [MyData] }
There are many ways for this response but in my opinion, is best practice is to use an interceptor
based on documentation
// src/common/interceptors/format-response.interceptor.ts
import { CallHandler, ExecutionContext, Injectable, NestInterceptor, HttpStatus } from '#nestjs/common';
import { map, Observable } from 'rxjs';
#Injectable()
export class FormatResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(value => {
value = (value) ? value : []
return { status: "success", data: [value]};
}));
}
}
and in the controller inject the interceptor
import { UseInterceptors } from '#nestjs/common';
#UseInterceptors(FormatResponseInterceptor)
export class UserController {
constructor() {}
#Get(':id')
async getOne(#Param('id') id: string) {
const model = await this.categoriesService.getById(id);
if (model) {
return model;
} else {
throw new NotFoundException('Category not found');
}
}
}
And for change the format of error, you can use Filter
I usually just explicitly write
try {
....
return { status: HttpStatus.OK, data: [MyData] }
} catch(e){
return { status: HttpStatus.NOT_FOUND, message: e.message || 'my error' }
}
I am struggling with calling the backend from angular. When I create a component I also get the parameter "category" from the URL like this:
export class ProductsComponent{
productList = []
category = ""
$params;
$products;
constructor(
private products: ProductsService,
private route: ActivatedRoute
){}
ngOnInit() {
this.$params = this.route.params.subscribe(params => {
this.category = params['category']
});
this.$products = this.products.products(this.category).subscribe(
productList => {
this.productList = productList.result
},
err => {
console.log(err)
}
)
}
ngOnDestroy(){
// DON'T FORGET TO UNSUBSCRIBE!!!
this.$params.unsubscribe();
this.$products.unsubscribe();
}
}
This works well, but now in the ProductsService, where I call the http.get I think it is not working fine.
#Injectable()
export class ProductsService {
constructor(private http: HttpClient, private router: Router) {}
public products(category: string): Observable<any> {
return this.http.get(`/products/getallproducts`, {headers: {'Content-Type': 'application/json'}, params: {'category': category}})
}
}
Because when I try to log the req.body.category in the backend, it says it is null. But it is not, it is the right value.
This is what I am trying to do in Node:
products.get(('/getallproducts'), (req, res) => {
let category = req.body.category;
console.log("REQ" + req.body)
if(category === "all") {
ProductModel.findAll()
.then(result => {
res.json({result: result})
})
.catch(err => {
res.json({error: err})
})
} else {
ProductModel.findAll({
where: {
productsubcategory: category
}
})
.then(result => {
res.json({result: result})
})
.catch(err => {
res.json({error: err})
})
}
})
Review this article: Todd MoTTo: Angular constructor versus ngOnInit
Then move your constructor code into your ngOnInit method.
// Add these;
$params;
$products;
constructor(
private products: ProductsService,
private route: ActivatedRoute
){}
ngOnInit() {
this.$params = this.route.params.subscribe(params => {
this.category = params['category']
});
this.$products = this.products.products(this.category).subscribe(
productList => {
this.productList = productList.result
},
err => {
console.log(err)
});
}
ngOnDestroy(){
// DON'T FORGET TO UNSUBSCRIBE!!!
this.$params.unsubscribe();
this.$products.unsubscribe();
}
Update: I see what you're doing now. It appears to be a bit backwards to me. First you are loading the component, then going to GET some backend data. If you are routing to something new that requires some data, then try a resolver. With a resolver, you can fetch new data on route change. It is up to you if you want to pause the resolver until you get data (and have a spinner on the link that was clicked), or show a loading screen and wait for it. But the resolver will load when the route is loaded and it will publish the result. Then listen for the resolver Observable in the component.
// In Routes
{
path: 'products/:category',
component: YourComponent,
resolve: {
data: ProductsResolver
}
},// rest of routes.
#Injectable()
export class ProductsResolver implements Resolve<any> {
constructor(
private http: HttpClient
){}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
return this.http.get('/products/getallproducts',
{
headers: {
'Content-Type': 'application/json'
},
params: {
'category': route.params.category
}
});
}
And the component then would be...
$products;
constructor(
private route: ActivatedRoute
){}
ngOnInit() {
this.$products = this.route.data.subscribe(productList => {
this.productList = productList.result;
},
err => {
console.log(err)
});
}
ngOnDestroy(){
this.$products.unsubscribe();
}
When I'm trying to subsrcibe to a post request, it always returns the TypeError: result is null
I'm using a Angular CLI that connects with a Spring boot application, with a simple login page. Where I want to save the header of my response in local storage
This is the stacktrace:
"LoginComponent.prototype.login/<#webpack-internal:///../../../../../src/app/components/login/login.component.ts:32:13\nSafeSubscriber.prototype.__tryOrUnsub#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:245:13\nSafeSubscriber.prototype.next#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:192:17\nSubscriber.prototype._next#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:133:9\nSubscriber.prototype.next#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:97:13\nMapSubscriber.prototype._next#webpack-internal:///../../../../rxjs/_esm5/operators/map.js:88:9\nSubscriber.prototype.next#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:97:13\nFilterSubscriber.prototype._next#webpack-internal:///../../../../rxjs/_esm5/operators/filter.js:92:13\nSubscriber.prototype.next#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:97:13\nMergeMapSubscriber.prototype.notifyNext#webpack-internal:///../../../../rxjs/_esm5/operators/mergeMap.js:156:13\nInnerSubscriber.prototype._next#webpack-internal:///../../../../rxjs/_esm5/InnerSubscriber.js:27:9\nSubscriber.prototype.next#webpack-internal:///../../../../rxjs/_esm5/Subscriber.js:97:13\nonLoad#webpack-internal:///../../../common/esm5/http.js:2310:21\nZoneDelegate.prototype.invokeTask#webpack-internal:///../../../../zone.js/dist/zone.js:421:17\nonInvokeTask#webpack-internal:///../../../core/esm5/core.js:4939:24\nZoneDelegate.prototype.invokeTask#webpack-internal:///../../../../zone.js/dist/zone.js:420:17\nZone.prototype.runTask#webpack-internal:///../../../../zone.js/dist/zone.js:188:28\nZoneTask.invokeTask#webpack-internal:///../../../../zone.js/dist/zone.js:496:24\ninvokeTask#webpack-internal:///../../../../zone.js/dist/zone.js:1517:9\nglobalZoneAwareCallback#webpack-internal:///../../../../zone.js/dist/zone.js:1543:17\n"
This is my login.service.ts:
const httpOptions = { headers: new HttpHeaders({'Content-type': 'application/json'}) };
#Injectable() export class LoginService {
private loginUrl = 'https://music-makers.herokuapp.com/login';
constructor(private http: HttpClient) { }
public login(user: User): Observable<any> {
return this.http.post(this.loginUrl, user, httpOptions); }
And my login.components.ts:
export class LoginComponent implements OnInit {
model: any = {};
constructor(private loginService: LoginService, public router: Router) {
}
ngOnInit() {
}
login() {
const user = <User>({
email: this.model.email,
password: this.model.password,
});
console.log('email: ' + user.email + '\npass: ' + user.password);
this.loginService.login(user)
.subscribe(
result => {
// Handle result
localStorage.setItem('Authorization', result.headers.get('Authorization'));
console.log(result);
},
error => {
// Handle error
console.log('Error');
},
() => {
console.log('complete');
// No errors, route to new page
}
);
}
}
Your service should be use map() to return as an observable collection
public login(user: User): Observable<any> {
return this.http.post(this.loginUrl, user, httpOptions)
.map(responce => <any>responce)
.catch(error => {
return Observable.throw(error);
});
}
I have a route guard like below
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authenticationSvc: AuthenticationService) { }
canActivate(): Observable<boolean> {
return this.authenticationSvc.getAuthenticatedUser().map(
r => {
if (this.authenticationSvc.isAuthenticated()) {
// logged in so return true
return true;
}
this.router.navigateByUrl('/login');
return false;
})
}
The issue is that sometimes getAuthenticatedUser returns a 401, and I have an http-interceptor that handles the 401 and redirect to the login page. The issue is that this .map never resolves because the http request throws an error, and the angular router gets stuck on this first routing request and can't handle the subsequent request from the interceptor. How can I handle this error and have the Observable returned resolve to false and keep things moving?
getAuthenticatedUser() {
let getUserObservable = this.http.get(ApiUrl + 'security/getAuthenticatedUser')
.map((res: any) => res.json())
.share()
//Get the result for the cache
getUserObservable.subscribe(
r => {
if (r.success) {
this.authenticatedUser = r.result.user;
}
});
//return the observable
return getUserObservable;
}
and http-intercepter below
export class HttpInterceptor extends Http {
authSvc: AuthenticationService;
lastClicked: any = 0;
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private _router: Router, private injector: Injector) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.request(url, options));
}
get(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.get(url, options));
}
post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.post(url, body, this.getRequestOptionArgs(options)));
}
put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.put(url, body, this.getRequestOptionArgs(options)));
}
delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
return this.intercept(super.delete(url, options));
}
getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs {
if (options == null) {
options = new RequestOptions();
}
if (options.headers == null) {
options.headers = new Headers();
}
options.headers.append('Content-Type', 'application/json');
return options;
}
intercept(observable: Observable<Response>): Observable<Response> {
return observable.catch((err, source) => {
//If we get a 401 from the api that means out FormsAuthenticationTicket has expired, clear the auth cookie and navigate back to terms page
if (err.status == 401) {
this._router.navigateByUrl('/login');
}
return Observable.throw(err);
});
}
You can catch errors and return Observable<bool> as follows:
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private router: Router, private authenticationSvc: AuthenticationService) { }
canActivate(): Observable<boolean> {
return this.authenticationSvc.getAuthenticatedUser().map(
r => {
if (this.authenticationSvc.isAuthenticated()) {
// logged in so return true
return true;
}
this.router.navigateByUrl('/login');
return false;
})
.catch((error: any) => {
this.router.navigateByUrl('/login');
return Observable.of(false);
});
}
I can't find my error.
app.module.ts
...
providers: [ValidateService,AuthService]
...
I do the following in my register.component.ts:
import {AuthService} from '../../services/auth.service';
...
constructor( private _validateService: ValidateService,
private _fms: FlashMessagesService,
private _authService: AuthService,
private _router: Router
) { }
...
ngOnInit() {
this._authService.uniqueUser({username:'zomh'}).subscribe(data => {
console.log("data.success: "+data.success);
if(!data.success) { // Username already exists
console.log('exists');
}
else {
console.log('does not exist');
}
});
}
Works as expected the user is already in the database therefore I get the a user exists in the console.
I do pretty pretty much the very same thing (I broke it down to this point) in my validate.service.ts:
import { AuthService } from './auth.service';
import { Injectable } from '#angular/core';
import { FormControl } from '#angular/forms';
#Injectable()
export class ValidateService {
constructor( public _authService: AuthService) { }
validateRegister(user) {
if(user.name == undefined || user.email == undefined || user.username == undefined || user.password == undefined)
return false;
else
return true;
}
validateEmailPattern(c: FormControl) {
const re = /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (re.test(c.value))
return null;
else
return {invalidPattern:true};
}
validateUsernamePattern(c: FormControl) {
const re = /^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/
if (re.test(c.value))
return null;
else
return {invalidPattern:true};
}
validateUsernameIsUnique (c: FormControl) {
let ret:any;
if (c.value.length >= 3)
{
console.log(c.value);
this._authService.uniqueUser({username:'zomh'}).subscribe(data => {
if(!data.success) { // Username already exists
console.log('call from service: exists');
}
else {
console.log('call from service: does not exist');
}
});
}
return {usernameIsTaken:true};
}
}
But here I get a Cannot read property _authService of undefined Exception
For me it looks like the service did not inject correctly. But I can't find my error.
Update 1:
So i did copy the auth Service call into the Constructor and its working. Therefore it has to be some this. related error (?) i can't get the value of this._authService from any other method outside of the constructor ?
#Injectable()
export class ValidateService {
constructor( private _authService: AuthService ) {
this._authService.uniqueUser({ username: 'zomh' }).subscribe(data => {
if (!data.success) { // Username already exists
console.log('call from service: exists');
}
else {
console.log('call from service: does not exist');
}
});
}
I dont think you can have a new line between #Injectable and export class ValidateService {
Try it without that line.
After reading an article I rewrote my method into an instance method:
validateUsernameIsUnique = (c: FormControl) => {
let ret: any;
if (c.value.length >= 3) {
this._authService.uniqueUser({ username: c.value }).subscribe(data => {
if (!data.success) { // Username already exists
console.log('call from service: exists');
}
else {
console.log('call from service: does not exist');
}
});
}
...
It fixed the problem. I am still not sure why this had to be done though, feel free to add knowledge