Muhammad Azam
Muhammad Azam

Reputation: 564

db migration User entity in Loopback4

I follwed Loopback 4 tutorial to secure Api with @loopback/authentication, @loopback/authentication-jwt. I created a entity todo to follow tutorial example, when I migrate db only todo table is created but User, UserCredential related Tables can't be migrated automatically, official example is given on github it's working fine but it uses in memory db with file.

Is there any way to create tables by migrating using @loopback/authentication-jwt/User entity?

Upvotes: 1

Views: 613

Answers (1)

DieNand
DieNand

Reputation: 21

I got stuck on the same issue for quite a while but eventually figured out how to do it for MySQL. The gist of it is that you need to create your own models and then override the service and models for User and UserCredentials that are built into the @loopback/authentication-jwt extension. This is actually explained on the github here

In my case what made this non-trivial was that I needed to figure out how to translate the models defined in the extension to data models that you can actually store in MySQL. Once you do so they will migrate correctly if they are in the models folder.

Here are some examples of what I did to get this working:

In the constructor of application.ts

// Mount authentication system
this.component(AuthenticationComponent);
// Mount jwt component
this.component(JWTAuthenticationComponent);
// Bind datasource
this.dataSource(NihongoWordListDataSource, UserServiceBindings.DATASOURCE_NAME);
// Bind user service
this.bind(UserServiceBindings.USER_SERVICE).toClass(AppUserService),
// Bind user and credentials repository
this.bind(UserServiceBindings.USER_REPOSITORY).toClass(
  AppUserRepository,
),
this.bind(UserServiceBindings.USER_CREDENTIALS_REPOSITORY).toClass(
  AppUserCredentialsRepository,
),

Custom User Service (basically directly copied from the github page only mine is called AppUser):

    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';
    // User --> MyUser
    import {AppUser} from '../models';
    // UserRepository --> MyUserRepository
    import { AppUserRepository } from '../repositories';
    
    export type Credentials = {
      email: string;
      password: string;
    };
    
    // User --> MyUser
    export class AppUserService implements UserService<AppUser, Credentials> {
      constructor(
        // UserRepository --> MyUserRepository
        @repository(AppUserRepository) public userRepository: AppUserRepository,
      ) {}

    // User --> MyUser
      async verifyCredentials(credentials: Credentials): Promise<AppUser> {
        const invalidCredentialsError = 'Invalid email or password.';

    const foundUser = await this.userRepository.findOne({
      where: {email: credentials.email},
    });
    if (!foundUser) {
      throw new HttpErrors.Unauthorized(invalidCredentialsError);
    }

    const credentialsFound = await this.userRepository.findCredentials(
      foundUser.id,
    );
    if (!credentialsFound) {
      throw new HttpErrors.Unauthorized(invalidCredentialsError);
    }

    const passwordMatched = await compare(
      credentials.password,
      credentialsFound.password,
    );

    if (!passwordMatched) {
      throw new HttpErrors.Unauthorized(invalidCredentialsError);
    }

    return foundUser;
  }

    // User --> MyUser
      convertToUserProfile(user: AppUser): UserProfile {
        return {
          [securityId]: user.id.toString(),
          name: user.username,
          id: user.id,
          email: user.email,
        };
      }
    }

AppUser Model:

import {Entity, model, property, hasOne} from '@loopback/repository';
import {AppUserCredentials} from './app-user-credentials.model';

@model({
  settings: {
    idInjection: false,
    mysql: {schema: 'nihongo_word_list', table: 'app_user'}
  }
})
export class AppUser extends Entity {
  @property({
    type: 'string',
    id: true,
    generated: false,
    defaultFn: 'uuidv4',
    mysql: {columnName: 'id', dataType: 'varchar', dataLength: 36, nullable: 'N'},
  })
  id: string;

  @property({
    type: 'string',
    mysql: {columnName: 'realm', dataType: 'varchar', dataLength: 36, nullable: 'Y'},
  })
  realm?: string;

  @property({
    type: 'string',
    mysql: {columnName: 'username', dataType: 'varchar', dataLength: 256, nullable: 'Y'},
  })
  username?: string;

  @property({
    type: 'string',
    required: true,
    mysql: {columnName: 'email', dataType: 'varchar', dataLength: 256, nullable: 'N'},
  })
  email: string;

  @property({
    type: 'boolean',
    required: true,
    mysql: {columnName: 'emailVerified', dataType: 'tinyint', dataLength: 1, nullable: 'Y', default: 0},
  })
  emailVerified?: boolean;

  @property({
    type: 'string',
    mysql: {columnName: 'verificationToken', dataType: 'varchar', dataLength: 256, nullable: 'N'},
  })
  verificationToken?: string;

  @hasOne(() => AppUserCredentials, {keyTo: 'userId'})
  userCredentials: AppUserCredentials;

  // Define well-known properties here

  // Indexer property to allow additional data
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  [prop: string]: any;

  constructor(data?: Partial<AppUser>) {
    super(data);
  }
}

export interface AppUserRelations {
  // describe navigational properties here
}

export type AppUserWithRelations = AppUser & AppUserRelations;

You need to do the same for AppUserCredentials.

You also need to set up the repository for your AppUser and AppUserCredentials models.

I hope this puts you on the right track. Note that you can and should use the following npm CLI methods while you're making changes to the migration. I just did this:

// clean your dist directory otherwise built stuff gets left behind and can prevent your app from starting
npm run clean
// rebuild your code into dist folder
npm run build
// migrate your db
npm run migrate

Good luck!

Upvotes: 2

Related Questions