LittleFish
LittleFish

Reputation: 101

switchMap operator doesn't execute the returned observable

I have an API (NestJS) and trying to build a login. It is using TypeOrm to access DB.

When the strategy class calls auth.validateUser method, I need to get a user from DB and compare the passwords using bcrypt. The problem is that switchMap is not subscribing to the returned observable.

Here's my code:

auth.service.ts

validateUser(email: string, password: string): Observable<User> {
    return this.userService.readByEmail(email).pipe(
        switchMap(user => {
            return new Observable<User>(subscriber => {
                if (!user) {
                    subscriber.next(null)
                    subscriber.complete()
                } else {
                    bcrypt.compare(password + global.userPepper, user.password)
                        .then(result => {
                            const { password, ...validUser } = user
                            if (result) subscriber.next(validUser as User)
                            else subscriber.next(null)
                            subscriber.complete()
                        })
                        .catch(err => {
                            console.log(err)
                            subscriber.next(null)
                            subscriber.complete()
                        })
                }
            })
        })
    )
}

user.service.ts

readByEmail(email: string): Observable<User> {
    return new Observable<User>(subscriber => {
        // This part of the code is never executed
        this.userRepo.findOne({ email }).then(user => subscriber.next(user))
    })
}

What is the problem and how can I solve it?

UPDATE:
The problem in userService.readByEmail was solved following the suggestions from Tobias S. answer. But the switchMap operator from authService was not being called. I solved it changing the strategy file, which calls my auth.validateUser, to return a promise and not a observable anymore and works.
local.strategy.ts
Before:

validate(email: string, password: string) {
    return this.auth.validateUser(email, password).pipe(
        map(user => {
            if (!user) {
                throw new UnauthorizedException()
            }

            return user
        })
    )
}

After:

validate(email: string, password: string) {
    return this.auth.validateUser(email, password).pipe(
        map(user => {
            if (!user) {
                throw new UnauthorizedException()
            }

            return user
        })
    ).toPromise()
}

Upvotes: 0

Views: 513

Answers (1)

Tobias S.
Tobias S.

Reputation: 23825

I think this code could be simplified a lot more. You don't really need to create any Observables yourself with new Observable().

In UserService you can simply create an Observable from the Promise with the from operator.

user.service.ts

readByEmail(email: string): Observable<User> {
    return from(this.userRepo.findOne({ email }))
}

In AuthService you can return EMPTY if there is no user and then return the Promise you are creating with bcrypt.

"If it accepts Observable, it accepts Promise"

auth.service.ts

validateUser(email: string, password: string): Observable<User> {
    return this.userService.readByEmail(email).pipe(
        switchMap(user => {
            if (!user) return EMPTY

            return bcrypt.compare(password + global.userPepper, user.password)
                .then(result => {
                    const { password, ...validUser } = user
                    if (result) return validUser as User
                 })
                .catch(console.log)
        })
    )
}

Upvotes: 1

Related Questions