UNPKG

@next-auth/typeorm-legacy-adapter

Version:

TypeORM (legacy) adapter for next-auth.

388 lines (387 loc) 13.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TypeORMLegacyAdapter = exports.getManager = exports.entities = void 0; const typeorm_1 = require("typeorm"); const defaultEntities = __importStar(require("./entities")); const utils_1 = require("./utils"); exports.entities = defaultEntities; let _dataSource; async function getManager(options) { const { dataSource, entities } = options; const config = { ...(0, utils_1.parseDataSourceConfig)(dataSource), entities: Object.values(entities), }; if (!_dataSource) _dataSource = new typeorm_1.DataSource(config); const manager = _dataSource === null || _dataSource === void 0 ? void 0 : _dataSource.manager; if (!manager.connection.isInitialized) { await manager.connection.initialize(); } if (process.env.NODE_ENV !== "production") { await (0, utils_1.updateConnectionEntities)(_dataSource, config.entities); } return manager; } exports.getManager = getManager; /** * ## Setup * * Configure Auth.js to use the TypeORM Adapter: * * ```javascript title="pages/api/auth/[...nextauth].js" * import NextAuth from "next-auth" * import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" * * * export default NextAuth({ * adapter: TypeORMLegacyAdapter("yourconnectionstring"), * ... * }) * ``` * * `TypeORMLegacyAdapter` takes either a connection string, or a [`ConnectionOptions`](https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md) object as its first parameter. * * ## Advanced usage * * ### Custom models * * The TypeORM adapter uses [`Entity` classes](https://github.com/typeorm/typeorm/blob/master/docs/entities.md) to define the shape of your data. * * If you want to override the default entities (for example to add a `role` field to your `UserEntity`), you will have to do the following: * * > This schema is adapted for use in TypeORM and based upon our main [schema](https://authjs.dev/reference/adapters#models) * * 1. Create a file containing your modified entities: * * (The file below is based on the [default entities](https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-typeorm-legacy/src/entities.ts)) * * ```diff title="lib/entities.ts" * import { * Entity, * PrimaryGeneratedColumn, * Column, * ManyToOne, * OneToMany, * ValueTransformer, * } from "typeorm" * * const transformer: Record<"date" | "bigint", ValueTransformer> = { * date: { * from: (date: string | null) => date && new Date(parseInt(date, 10)), * to: (date?: Date) => date?.valueOf().toString(), * }, * bigint: { * from: (bigInt: string | null) => bigInt && parseInt(bigInt, 10), * to: (bigInt?: number) => bigInt?.toString(), * }, * } * * @Entity({ name: "users" }) * export class UserEntity { * @PrimaryGeneratedColumn("uuid") * id!: string * * @Column({ type: "varchar", nullable: true }) * name!: string | null * * @Column({ type: "varchar", nullable: true, unique: true }) * email!: string | null * * @Column({ type: "varchar", nullable: true, transformer: transformer.date }) * emailVerified!: string | null * * @Column({ type: "varchar", nullable: true }) * image!: string | null * * + @Column({ type: "varchar", nullable: true }) * + role!: string | null * * @OneToMany(() => SessionEntity, (session) => session.userId) * sessions!: SessionEntity[] * * @OneToMany(() => AccountEntity, (account) => account.userId) * accounts!: AccountEntity[] * } * * @Entity({ name: "accounts" }) * export class AccountEntity { * @PrimaryGeneratedColumn("uuid") * id!: string * * @Column({ type: "uuid" }) * userId!: string * * @Column() * type!: string * * @Column() * provider!: string * * @Column() * providerAccountId!: string * * @Column({ type: "varchar", nullable: true }) * refresh_token!: string | null * * @Column({ type: "varchar", nullable: true }) * access_token!: string | null * * @Column({ * nullable: true, * type: "bigint", * transformer: transformer.bigint, * }) * expires_at!: number | null * * @Column({ type: "varchar", nullable: true }) * token_type!: string | null * * @Column({ type: "varchar", nullable: true }) * scope!: string | null * * @Column({ type: "varchar", nullable: true }) * id_token!: string | null * * @Column({ type: "varchar", nullable: true }) * session_state!: string | null * * @Column({ type: "varchar", nullable: true }) * oauth_token_secret!: string | null * * @Column({ type: "varchar", nullable: true }) * oauth_token!: string | null * * @ManyToOne(() => UserEntity, (user) => user.accounts, { * createForeignKeyConstraints: true, * }) * user!: UserEntity * } * * @Entity({ name: "sessions" }) * export class SessionEntity { * @PrimaryGeneratedColumn("uuid") * id!: string * * @Column({ unique: true }) * sessionToken!: string * * @Column({ type: "uuid" }) * userId!: string * * @Column({ transformer: transformer.date }) * expires!: string * * @ManyToOne(() => UserEntity, (user) => user.sessions) * user!: UserEntity * } * * @Entity({ name: "verification_tokens" }) * export class VerificationTokenEntity { * @PrimaryGeneratedColumn("uuid") * id!: string * * @Column() * token!: string * * @Column() * identifier!: string * * @Column({ transformer: transformer.date }) * expires!: string * } * ``` * * 2. Pass them to `TypeORMLegacyAdapter` * * ```javascript title="pages/api/auth/[...nextauth].js" * import NextAuth from "next-auth" * import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" * import * as entities from "lib/entities" * * export default NextAuth({ * adapter: TypeORMLegacyAdapter("yourconnectionstring", { entities }), * ... * }) * ``` * * :::tip Synchronize your database ♻ * The `synchronize: true` option in TypeORM will generate SQL that exactly matches the entities. This will automatically apply any changes it finds in the entity model. This is a useful option in development. * ::: * * :::warning Using synchronize in production * `synchronize: true` should not be enabled against production databases as it may cause data loss if the configured schema does not match the expected schema! We recommend that you synchronize/migrate your production database at build-time. * ::: * * ### Naming Conventions * * If mixed snake_case and camelCase column names are an issue for you and/or your underlying database system, we recommend using TypeORM's naming strategy feature to change the target field names. There is a package called `typeorm-naming-strategies` which includes a `snake_case` strategy which will translate the fields from how Auth.js expects them, to snake_case in the actual database. * * For example, you can add the naming convention option to the connection object in your NextAuth config. * * ```javascript title="pages/api/auth/[...nextauth].js" * import NextAuth from "next-auth" * import { TypeORMLegacyAdapter } from "@next-auth/typeorm-legacy-adapter" * import { SnakeNamingStrategy } from 'typeorm-naming-strategies' * import { ConnectionOptions } from "typeorm" * * const connection: ConnectionOptions = { * type: "mysql", * host: "localhost", * port: 3306, * username: "test", * password: "test", * database: "test", * namingStrategy: new SnakeNamingStrategy() * } * * export default NextAuth({ * adapter: TypeORMLegacyAdapter(connection), * ... * }) * ``` */ function TypeORMLegacyAdapter(dataSource, options) { var _a, _b, _c, _d; const entities = options === null || options === void 0 ? void 0 : options.entities; const c = { dataSource, entities: { UserEntity: (_a = entities === null || entities === void 0 ? void 0 : entities.UserEntity) !== null && _a !== void 0 ? _a : defaultEntities.UserEntity, SessionEntity: (_b = entities === null || entities === void 0 ? void 0 : entities.SessionEntity) !== null && _b !== void 0 ? _b : defaultEntities.SessionEntity, AccountEntity: (_c = entities === null || entities === void 0 ? void 0 : entities.AccountEntity) !== null && _c !== void 0 ? _c : defaultEntities.AccountEntity, VerificationTokenEntity: (_d = entities === null || entities === void 0 ? void 0 : entities.VerificationTokenEntity) !== null && _d !== void 0 ? _d : defaultEntities.VerificationTokenEntity, }, }; return { /** * Method used in testing. You won't need to call this in your app. * @internal */ async __disconnect() { const m = await getManager(c); await m.connection.close(); }, // @ts-expect-error createUser: async (data) => { const m = await getManager(c); const user = await m.save("UserEntity", data); return user; }, // @ts-expect-error async getUser(id) { const m = await getManager(c); const user = await m.findOne("UserEntity", { where: { id } }); if (!user) return null; return { ...user }; }, // @ts-expect-error async getUserByEmail(email) { const m = await getManager(c); const user = await m.findOne("UserEntity", { where: { email } }); if (!user) return null; return { ...user }; }, async getUserByAccount(provider_providerAccountId) { var _a; const m = await getManager(c); const account = await m.findOne("AccountEntity", { where: provider_providerAccountId, relations: ["user"] }); if (!account) return null; return (_a = account.user) !== null && _a !== void 0 ? _a : null; }, // @ts-expect-error async updateUser(data) { const m = await getManager(c); const user = await m.save("UserEntity", data); return user; }, async deleteUser(id) { const m = await getManager(c); await m.transaction(async (tm) => { await tm.delete("AccountEntity", { userId: id }); await tm.delete("SessionEntity", { userId: id }); await tm.delete("UserEntity", { id }); }); }, async linkAccount(data) { const m = await getManager(c); const account = await m.save("AccountEntity", data); return account; }, async unlinkAccount(providerAccountId) { const m = await getManager(c); await m.delete("AccountEntity", providerAccountId); }, async createSession(data) { const m = await getManager(c); const session = await m.save("SessionEntity", data); return session; }, async getSessionAndUser(sessionToken) { const m = await getManager(c); const sessionAndUser = await m.findOne("SessionEntity", { where: { sessionToken }, relations: ["user"] }); if (!sessionAndUser) return null; const { user, ...session } = sessionAndUser; return { session, user }; }, async updateSession(data) { const m = await getManager(c); await m.update("SessionEntity", { sessionToken: data.sessionToken }, data); // TODO: Try to return? return null; }, async deleteSession(sessionToken) { const m = await getManager(c); await m.delete("SessionEntity", { sessionToken }); }, async createVerificationToken(data) { const m = await getManager(c); const verificationToken = await m.save("VerificationTokenEntity", data); // @ts-expect-error delete verificationToken.id; return verificationToken; }, // @ts-expect-error async useVerificationToken(identifier_token) { const m = await getManager(c); const verificationToken = await m.findOne("VerificationTokenEntity", { where: identifier_token, }); if (!verificationToken) { return null; } await m.delete("VerificationTokenEntity", identifier_token); // @ts-expect-error delete verificationToken.id; return verificationToken; }, }; } exports.TypeORMLegacyAdapter = TypeORMLegacyAdapter;