Next-auth: How to change "int" to "uuid"?

Created on 8 Sep 2020  路  5Comments  路  Source: nextauthjs/next-auth

Your question
I would like to know how to change the model User column id from "int" to "uuid", how is it possible?

This example was mentioned here, but never created:
https://github.com/nextauthjs/next-auth/issues/283

What are you trying to do
I tried to customise it following the example:
https://next-auth.js.org/tutorials/typeorm-custom-models#creating-custom-models

I tried the following:

import { EntitySchemaOptions } from 'typeorm/entity-schema/EntitySchemaOptions';
import Adapters from 'next-auth/adapters';

// Extend the built-in models using class inheritance
export default class User extends Adapters.TypeORM.Models.User.model {
  // You can extend the options in a model but you should not remove the base
  // properties or change the order of the built-in options on the constructor
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
  constructor(name, email, image, emailVerified) {
    super(name, email, image, emailVerified);
  }
}

export const UserSchema: EntitySchemaOptions<any> = {
  name: 'User',
  target: User,
  columns: {
    ...Adapters.TypeORM.Models.User.schema.columns,
    id: {
      primary: true,
      type: 'uuid',
      generated: true
    }
  }
};

but got the following error:

[next-auth][error][callback_email_error] QueryFailedError: column "id" is of type uuid but default expression is of type bigint
    at new QueryFailedError (/Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/typeorm/error/QueryFailedError.js:11:28)
    at Query.callback (/Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:176:38)
    at Query.handleError (/Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/pg/lib/query.js:139:19)
    at Client._handleErrorMessage (/Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/pg/lib/client.js:326:17)
    at Connection.emit (events.js:314:20)
    at /Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/pg/lib/connection.js:109:12
    at Parser.parse (/Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/pg-protocol/dist/parser.js:40:17)
    at Socket.<anonymous> (/Users/guilhermeramos/Desktop/projects.nosync/snuper/web/node_modules/pg-protocol/dist/index.js:8:42)
    at Socket.emit (events.js:314:20)
    at addChunk (_stream_readable.js:303:12) {
  length: 173,
  severity: 'ERROR',
  code: '42804',
  detail: undefined,
  hint: 'You will need to rewrite or cast the expression.',
  position: undefined,
  internalPosition: undefined,
  internalQuery: undefined,
  where: undefined,
  schema: undefined,
  table: undefined,
  column: undefined,
  dataType: undefined,
  constraint: undefined,
  file: 'heap.c',
  line: '2956',
  routine: 'cookDefault',
  query: `ALTER TABLE "users" ALTER COLUMN "id" SET DEFAULT nextval('users_id_seq')`,
  parameters: []
} 

this is my config:

import NextAuth, { InitOptions } from 'next-auth';
import Providers from 'next-auth/providers';
import Adapters from 'next-auth/adapters';
import Models from '../../../models';

const options: InitOptions = {
  providers: [
    Providers.Email({
      server: {
        host: process.env.NEXT_PUBLIC_EMAIL_SERVER_HOST || '',
        port: Number(process.env.NEXT_PUBLIC_EMAIL_SERVER_PORT),
        auth: {
          user: process.env.NEXT_PUBLIC_EMAIL_SERVER_USER || '',
          pass: process.env.NEXT_PUBLIC_EMAIL_SERVER_PASSWORD || ''
        }
      },
      from: process.env.EMAIL_FROM
    })
  ],
  adapter: Adapters.TypeORM.Adapter(
    // The first argument should be a database connection string or TypeORM config object
    {
      type: 'postgres',
      database: String(process.env.NEXT_PUBLIC_DATABASE_DATABASE),
      host: String(process.env.NEXT_PUBLIC_DATABASE_HOST),
      port: Number(process.env.NEXT_PUBLIC_DATABASE_PORT),
      username: String(process.env.NEXT_PUBLIC_DATABASE_USERNAME),
      password: String(process.env.NEXT_PUBLIC_DATABASE_PASSWORD),
      synchronize: true
    },
    // The second argument can be used to pass custom models and schemas
    // @ts-ignore
    {
      models: {
        User: Models.User
        // VerificationRequest: Models.VerificationRequest
      }
    }
  ),
  session: {
    jwt: true,
    maxAge: 30 * 24 * 60 * 60, // 30 days
    updateAge: 24 * 60 * 60 // 24 hours
  },
  pages: {
    signIn: '/login',
    signOut: '/login',
    error: '/auth/error', // Error code passed in query string as ?error=
    // verifyRequest: '/auth/verify-request', // (used for check email message)
    newUser: '/login' // If set, new users will be directed here on first sign in
  }
};

export default (req, res) => NextAuth(req, res, options);

Feedback
Documentation refers to searching through online documentation, code comments and issue history. The example project refers to next-auth-example.

  • [ ] Found the documentation helpful
  • [x] Found documentation but was incomplete
  • [ ] Could not find relevant documentation
  • [ ] Found the example project helpful
  • [x] Did not find the example project helpful
help wanted question

All 5 comments

My workaround:

I create the entities on the nodejs server as follows:

  • pages/api/auth/[...nextauth].ts
import NextAuth, { InitOptions } from 'next-auth';
import Providers from 'next-auth/providers';

const options: InitOptions = {
  providers: [
    Providers.Email({
      server: {
        host: process.env.NEXT_PUBLIC_EMAIL_SERVER_HOST || '',
        port: Number(process.env.NEXT_PUBLIC_EMAIL_SERVER_PORT),
        auth: {
          user: process.env.NEXT_PUBLIC_EMAIL_SERVER_USER || '',
          pass: process.env.NEXT_PUBLIC_EMAIL_SERVER_PASSWORD || ''
        }
      },
      from: process.env.EMAIL_FROM
    })
  ],
  database: {
    type: 'postgres',
    database: String(process.env.NEXT_PUBLIC_DATABASE_DATABASE),
    host: String(process.env.NEXT_PUBLIC_DATABASE_HOST),
    port: Number(process.env.NEXT_PUBLIC_DATABASE_PORT),
    username: String(process.env.NEXT_PUBLIC_DATABASE_USERNAME),
    password: String(process.env.NEXT_PUBLIC_DATABASE_PASSWORD),
    // synchronize: true,
    logging: true
  },
  session: {
    jwt: true,
    maxAge: 30 * 24 * 60 * 60, // 30 days
    updateAge: 24 * 60 * 60 // 24 hours
  },
  pages: {
    signIn: '/login',
    signOut: '/login',
    error: '/auth/error', // Error code passed in query string as ?error=
    // verifyRequest: '/auth/verify-request', // (used for check email message)
    newUser: '/login' // If set, new users will be directed here on first sign in
  }
};

export default (req, res) => NextAuth(req, res, options);

On my NodeJS server that uses typeorm:

  • VerificationRequests
import {
  BaseEntity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  Entity,
  Index
} from 'typeorm';

@Entity()
class VerificationRequests extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ length: 255 })
  identifier: string;

  @Index({ unique: true })
  @Column({ length: 255 })
  token: string;

  @Column()
  expires: Date;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}

export default VerificationRequests;

  • Users
import {
  BaseEntity,
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
  Index,
  ManyToOne,
  JoinTable,
  ManyToMany
} from 'typeorm';
import { IProfile } from 'src/types/graphql';

@Entity()
class Users extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ length: 255, nullable: true })
  name: string;

  @Index({ unique: true })
  @Column({ length: 255 })
  email: string;

  @Column({ nullable: true })
  email_verified: Date;

  @Column({ length: 255, nullable: true })
  image: string;

  @ManyToOne('Profile')
  selectedProfile: IProfile;

  @ManyToMany('Profile', 'users')
  @JoinTable()
  profiles: IProfile[];

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}

export default Users;
  • Accounts
import {
  BaseEntity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  Entity,
  Index
} from 'typeorm';
import Users from '../User/Users.postgres';

@Entity()
class Accounts extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Index({ unique: true })
  @Column({ length: 255 })
  compound_id: string;

  @Index()
  @Column('uuid')
  user_id: Users;

  @Column({ length: 255 })
  provider_type: string;

  @Index()
  @Column({ length: 255 })
  provider_id: string;

  @Index()
  @Column({ length: 255 })
  provider_account_id: string;

  @Column({ type: 'text', nullable: true })
  refresh_token: string;

  @Column({ type: 'text', nullable: true })
  access_token: string;

  @Column({ nullable: true })
  access_token_expires: Date;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}

export default Accounts;
  • Sessions
import {
  BaseEntity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  UpdateDateColumn,
  Entity,
  Index
} from 'typeorm';

@Entity()
class Sessions extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('uuid')
  user_id: string;

  @Column()
  expires: Date;

  @Index({ unique: true })
  @Column({ length: 255 })
  session_token: string;

  @Index({ unique: true })
  @Column({ length: 255 })
  access_token: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  updated_at: Date;
}

export default Sessions;

hey there I'm running into a similiar problem too. Just curious when you said on server (and not nextjs) did you mean you have another auth server just for user login?

hey there I'm running into a similiar problem too. Just curious when you said on server (and not nextjs) did you mean you have another auth server just for user login?

hey, sorry for the delay, yeah we have a nodejs server with typeorm also where we manage our other services... this nodejs server is connected to same db table and then I created the schemas as shown above...

this is an example of the server: https://github.com/guiaramos/ts-graphql-server

so in this example, we could create the folders inside of entity with those schema, ok?

I updated my comment with current schema that I am using and working well until now...

@iaincollins is there a way to do this without a seperate server for the database - i.e just within the next.js application? I guess you would have fork the package and add @guiaramos 's change (shown below), directly to the package?

class Sessions extends BaseEntity {
  @PrimaryGeneratedColumn('uuid')
  id: string;

@archywillhe I solved this by adding a second column to the table with UUID's so I can better look up users and pass the value around without worrying about someone querying the next sequential id.

Following the same guide, https://next-auth.js.org/tutorials/typeorm-custom-models#creating-custom-models , in /models/User.js add:

import Adapters from 'next-auth/adapters';
import { v4 as uuidv4 } from 'uuid';

// Extend the built-in models using class inheritance
export default class User extends Adapters.TypeORM.Models.User.model {
  // You can extend the options in a model but you should not remove the base
  // properties or change the order of the built-in options on the constructor
  constructor(name, email, image, emailVerified) {
    super(name, email, image, emailVerified);
  }
}

export const UserSchema = {
  name: 'User',
  target: User,
  columns: {
    ...Adapters.TypeORM.Models.User.schema.columns,
    //add new schema columns
    internalId: {
      type: 'uuid',
      isPrimary: false,
      generationStrategy: 'uuid',
      default: uuidv4(),
      unique: true
    },
  }
};

Hope this helps anyone although I realise it may not be the best solution, open to suggestions 馃憤馃徑

Was this page helpful?
0 / 5 - 0 ratings