Reputation: 55
I have a problem with loopback4, especially when I try to create Custom Authentication UserService.
I've tried to define my own JWTStrategy
and JWTService
but I have same error.
I follow the docs (https://loopback.io/doc/en/lb4/JWT-authentication-extension.html#stability-%EF%B8%8Fexperimental%EF%B8%8F) but return an error when I run my API
Argument of type 'typeof MyUserService' is not assignable to parameter of type 'Constructor<UserService<User, Credentials>>'.
Construct signature return types 'MyUserService' and 'UserService<User, Credentials>' are incompatible
This is my elements
PostgreSQL database => users table
CREATE TABLE public.users (
username CHARACTER VARYING(10) NOT NULL UNIQUE,
password CHARACTER VARYING(255) NOT NULL,
PRIMARY KEY (username)
);
user.model.ts
import {Entity, model, property} from '@loopback/repository';
@model({settings: {strict: false}})
export class MyUser extends Entity {
@property({
type: 'string',
id: true,
generated: false,
required: true,
})
username: string;
@property({
type: 'string',
required: true,
})
password: string;
[prop: string]: any;
constructor(data?: Partial<MyUser>) {
super(data);
}
}
export interface UserRelations {
// describe navigational properties here
}
export type UserWithRelations = MyUser & UserRelations;
user.repository.ts
import {DefaultCrudRepository} from '@loopback/repository';
import {MyUser, UserRelations} from '../models';
import {DbDataSource} from '../datasources';
import {inject} from '@loopback/core';
export class MyUserRepository extends DefaultCrudRepository<
MyUser,
typeof MyUser.prototype.username,
UserRelations
> {
constructor(
@inject('datasources.db') dataSource: DbDataSource,
) {
super(MyUser, dataSource);
}
}
user.service.ts
import {UserService} from '@loopback/authentication';
import {repository} from '@loopback/repository';
import {HttpErrors} from '@loopback/rest';
import {securityId, UserProfile} from '@loopback/security';
import {compare} from 'bcryptjs';
import {MyUser} from '../models';
import {MyUserRepository} from '../repositories';
export type Credentials = {
username: string;
password: string;
}
export class MyUserService implements UserService<MyUser, Credentials> {
constructor(
@repository(MyUserRepository) public userRepository: MyUserRepository
) {}
async verifyCredentials(credentials: Credentials): Promise<MyUser> {
const foundUser = await this.userRepository.findOne({
where: {
username: credentials.username
}
})
if (!foundUser) throw new HttpErrors['NotFound']('Invalid creds');
const isMatch = await compare(credentials.password, foundUser.password);
if (!isMatch) throw new HttpErrors.Unauthorized('Password not match');
return foundUser;
}
convertToUserProfile(user: MyUser): UserProfile {
return {
[securityId]: user.username,
username: user.username
}
}
}
and my application.ts file
import {BootMixin} from '@loopback/boot';
import {ApplicationConfig} from '@loopback/core';
import {
RestExplorerBindings,
RestExplorerComponent,
} from '@loopback/rest-explorer';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {ServiceMixin} from '@loopback/service-proxy';
import path from 'path';
import {MySequence} from './sequence';
import {
AuthenticationComponent,
} from '@loopback/authentication';
import {
JWTAuthenticationComponent,
UserServiceBindings
} from '@loopback/authentication-jwt';
import {
DbDataSource
} from './datasources';
import {MyUserService} from './services';
import {MyUserRepository} from './repositories';
export {ApplicationConfig};
export class PsqlTodoApplication extends BootMixin(
ServiceMixin(RepositoryMixin(RestApplication)),
) {
constructor(options: ApplicationConfig = {}) {
super(options);
// Set up the custom sequence
this.sequence(MySequence);
// Set up default home page
this.static('/', path.join(__dirname, '../public'));
// Customize @loopback/rest-explorer configuration here
this.configure(RestExplorerBindings.COMPONENT).to({
path: '/explorer',
});
this.component(RestExplorerComponent);
this.component(AuthenticationComponent);
this.component(JWTAuthenticationComponent);
this.dataSource(DbDataSource, UserServiceBindings.DATASOURCE_NAME);
this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService); // <= error is here!
this.bind(UserServiceBindings.USER_REPOSITORY).toClass(MyUserRepository);
this.projectRoot = __dirname;
// Customize @loopback/boot Booter Conventions here
this.bootOptions = {
controllers: {
// Customize ControllerBooter Conventions here
dirs: ['controllers'],
extensions: ['.controller.js'],
nested: true,
},
};
}
}
How can I create my own Credentials with username/password and not reuse Credentials from @loopback/authentication-jwt
?
Upvotes: 1
Views: 578
Reputation: 829
These kind of errors happen when your custom UserService
type has member variables of different types from its corresponding interface, or member functions with different signatures - including return types - are different from the interface. Also this can happen with any custom implementation and its corresponding interface.
Also, services you write, including custom JwtService
classes, are bound in the application.ts
file like this:
this.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JwtService);
Obviously this binding is for the token service, but this applies to any other built-in loopback service.
This discussion might also be of interest.
Upvotes: 0