From deb1d457e1a2f60815d713cfb5c7bf76cf843881 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 10:32:33 +0200 Subject: [PATCH 01/13] migration adds is_admin column to users --- database/migrations/0034-drop_server_user_table.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 database/migrations/0034-drop_server_user_table.ts diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts new file mode 100644 index 000000000..2e0a789ac --- /dev/null +++ b/database/migrations/0034-drop_server_user_table.ts @@ -0,0 +1,13 @@ +/* MIGRATION DROP server_users TABLE +add isAdmin COLUMN to users TABLE */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` boolean DEFAULT false AFTER `language`;') +} + +export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') +} From 58ab92ff63ba88071a02aaf807eff54044f096a1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:11:40 +0200 Subject: [PATCH 02/13] drop server users --- .../migrations/0034-drop_server_user_table.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts index 2e0a789ac..914c75457 100644 --- a/database/migrations/0034-drop_server_user_table.ts +++ b/database/migrations/0034-drop_server_user_table.ts @@ -6,8 +6,32 @@ add isAdmin COLUMN to users TABLE */ export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` boolean DEFAULT false AFTER `language`;') + + await queryFn( + 'UPDATE `users` SET `is_admin` = true WHERE `email` IN (SELECT `email` FROM `server_users`);', + ) + + await queryFn('DROP TABLE `server_users`;') } export async function downgrade(queryFn: (query: string, values?: any[]) => Promise>) { + await queryFn(` + CREATE TABLE IF NOT EXISTS \`server_users\` ( + \`id\` int(10) unsigned NOT NULL AUTO_INCREMENT, + \`username\` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + \`password\` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + \`email\` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + \`role\` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'admin', + \`activated\` tinyint(4) NOT NULL DEFAULT '0', + \`last_login\` datetime DEFAULT NULL, + \`created\` datetime NOT NULL, + \`modified\` datetime NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`) + + await queryFn( + 'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `created`, `created` FROM `users` WHERE `is_admin` = true;', + ) + await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') } From ca4a92d321d804dda4c8c4a79b0bea716616ff1b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:19:02 +0200 Subject: [PATCH 03/13] add isAdmin entity to user db model --- .../0034-drop_server_user_table/User.ts | 81 +++++++++++++++++++ database/entity/User.ts | 2 +- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 database/entity/0034-drop_server_user_table/User.ts diff --git a/database/entity/0034-drop_server_user_table/User.ts b/database/entity/0034-drop_server_user_table/User.ts new file mode 100644 index 000000000..9192b92ff --- /dev/null +++ b/database/entity/0034-drop_server_user_table/User.ts @@ -0,0 +1,81 @@ +import { + BaseEntity, + Entity, + PrimaryGeneratedColumn, + Column, + OneToMany, + DeleteDateColumn, +} from 'typeorm' +import { UserSetting } from '../UserSetting' + +@Entity('users', { engine: 'InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci' }) +export class User extends BaseEntity { + @PrimaryGeneratedColumn('increment', { unsigned: true }) + id: number + + @Column({ name: 'public_key', type: 'binary', length: 32, default: null, nullable: true }) + pubKey: Buffer + + @Column({ name: 'privkey', type: 'binary', length: 80, default: null, nullable: true }) + privKey: Buffer + + @Column({ length: 255, unique: true, nullable: false, collation: 'utf8mb4_unicode_ci' }) + email: string + + @Column({ + name: 'first_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + firstName: string + + @Column({ + name: 'last_name', + length: 255, + nullable: true, + default: null, + collation: 'utf8mb4_unicode_ci', + }) + lastName: string + + @DeleteDateColumn() + deletedAt: Date | null + + @Column({ type: 'bigint', default: 0, unsigned: true }) + password: BigInt + + @Column({ name: 'email_hash', type: 'binary', length: 32, default: null, nullable: true }) + emailHash: Buffer + + @Column({ name: 'created', default: () => 'CURRENT_TIMESTAMP', nullable: false }) + createdAt: Date + + @Column({ name: 'email_checked', type: 'bool', nullable: false, default: false }) + emailChecked: boolean + + @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false }) + language: string + + @Column({ name: 'is_admin', type: 'bool', nullable: false, default: false }) + isAdmin: boolean + + @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) + referrerId?: number | null + + @Column({ name: 'publisher_id', default: 0 }) + publisherId: number + + @Column({ + type: 'text', + name: 'passphrase', + collation: 'utf8mb4_unicode_ci', + nullable: true, + default: null, + }) + passphrase: string + + @OneToMany(() => UserSetting, (userSetting) => userSetting.user) + settings: UserSetting[] +} diff --git a/database/entity/User.ts b/database/entity/User.ts index 35dfb7bbe..4cd68174c 100644 --- a/database/entity/User.ts +++ b/database/entity/User.ts @@ -1 +1 @@ -export { User } from './0033-add_referrer_id/User' +export { User } from './0034-drop_server_user_table/User' From 5c6aa5d4ce715911dd60128ac6448b4c807e9c6e Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:20:28 +0200 Subject: [PATCH 04/13] new db version for isAdmin in user db model --- backend/src/config/index.ts | 2 +- backend/src/util/communityUser.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 91f450369..4bf550038 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ Decimal.set({ }) const constants = { - DB_VERSION: '0033-add_referrer_id', + DB_VERSION: '0034-drop_server_user_table', DECAY_START_TIME: new Date('2021-05-13 17:46:31'), // GMT+0 CONFIG_VERSION: { DEFAULT: 'DEFAULT', diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 33ac2fad2..8b75b37a1 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -17,6 +17,7 @@ const communityDbUser: dbUser = { createdAt: new Date(), emailChecked: false, language: '', + isAdmin: false, publisherId: 0, passphrase: '', settings: [], From 64859a71f44457afc97b31f2fbe225451dedb90b Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:27:09 +0200 Subject: [PATCH 05/13] use isAdmin on user to determine if user is admin --- backend/src/graphql/model/User.ts | 8 ++++---- backend/src/graphql/resolver/UserResolver.ts | 10 ---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 1a187a38f..1949592c0 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -14,8 +14,8 @@ export class User { this.emailChecked = user.emailChecked this.language = user.language this.publisherId = user.publisherId + this.isAdmin = user.isAdmin // TODO - this.isAdmin = null this.coinanimation = null this.klickTipp = null this.hasElopage = null @@ -58,11 +58,11 @@ export class User { // `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, + @Field(() => Boolean) + isAdmin: boolean + // TODO this is a bit inconsistent with what we query from the database // therefore all those fields are now nullable with default value null - @Field(() => Boolean, { nullable: true }) - isAdmin: boolean | null - @Field(() => Boolean, { nullable: true }) coinanimation: boolean | null diff --git a/backend/src/graphql/resolver/UserResolver.ts b/backend/src/graphql/resolver/UserResolver.ts index 137c09622..7685268b4 100644 --- a/backend/src/graphql/resolver/UserResolver.ts +++ b/backend/src/graphql/resolver/UserResolver.ts @@ -19,9 +19,7 @@ import { sendResetPasswordEmail as sendResetPasswordEmailMailer } from '@/mailer import { sendAccountActivationEmail } from '@/mailer/sendAccountActivationEmail' import { klicktippSignIn } from '@/apis/KlicktippController' import { RIGHTS } from '@/auth/RIGHTS' -import { ROLE_ADMIN } from '@/auth/ROLES' import { hasElopageBuys } from '@/util/hasElopageBuys' -import { ServerUser } from '@entity/ServerUser' // eslint-disable-next-line @typescript-eslint/no-var-requires const sodium = require('sodium-native') @@ -207,7 +205,6 @@ export class UserResolver { }) user.coinanimation = coinanimation - user.isAdmin = context.role === ROLE_ADMIN return user } @@ -243,9 +240,6 @@ export class UserResolver { } const user = new User(dbUser) - // user.email = email - // user.pubkey = dbUser.pubKey.toString('hex') - user.language = dbUser.language // Elopage Status & Stored PublisherId user.hasElopage = await this.hasElopage({ ...context, user: dbUser }) @@ -266,10 +260,6 @@ export class UserResolver { }) user.coinanimation = coinanimation - // context.role is not set to the actual role yet on login - const countServerUsers = await ServerUser.count({ email: user.email }) - user.isAdmin = countServerUsers > 0 - context.setHeaders.push({ key: 'token', value: encode(dbUser.pubKey), From 8ca72beac8cdbd5ebf112f90d09652e398ad41c5 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:30:29 +0200 Subject: [PATCH 06/13] use isAdmin of user to determine user role --- backend/src/graphql/directive/isAuthorized.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/graphql/directive/isAuthorized.ts b/backend/src/graphql/directive/isAuthorized.ts index 84756c45a..065c01957 100644 --- a/backend/src/graphql/directive/isAuthorized.ts +++ b/backend/src/graphql/directive/isAuthorized.ts @@ -8,7 +8,6 @@ import { RIGHTS } from '@/auth/RIGHTS' import { getCustomRepository } from '@dbTools/typeorm' import { UserRepository } from '@repository/User' import { INALIENABLE_RIGHTS } from '@/auth/INALIENABLE_RIGHTS' -import { ServerUser } from '@entity/ServerUser' const isAuthorized: AuthChecker = async ({ context }, rights) => { context.role = ROLE_UNAUTHORIZED // unauthorized user @@ -36,8 +35,7 @@ const isAuthorized: AuthChecker = async ({ context }, rights) => { try { const user = await userRepository.findByPubkeyHex(context.pubKey) context.user = user - const countServerUsers = await ServerUser.count({ email: user.email }) - context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER + context.role = user.isAdmin ? ROLE_ADMIN : ROLE_USER } catch { // in case the database query fails (user deleted) throw new Error('401 Unauthorized') From 9136c4596dad00460523b789718ede5871fa80ab Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:32:30 +0200 Subject: [PATCH 07/13] remove server user entity --- database/entity/ServerUser.ts | 1 - database/entity/index.ts | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 database/entity/ServerUser.ts diff --git a/database/entity/ServerUser.ts b/database/entity/ServerUser.ts deleted file mode 100644 index 495513823..000000000 --- a/database/entity/ServerUser.ts +++ /dev/null @@ -1 +0,0 @@ -export { ServerUser } from './0001-init_db/ServerUser' diff --git a/database/entity/index.ts b/database/entity/index.ts index cb6f56ab0..542333755 100644 --- a/database/entity/index.ts +++ b/database/entity/index.ts @@ -1,7 +1,6 @@ import { LoginElopageBuys } from './LoginElopageBuys' import { LoginEmailOptIn } from './LoginEmailOptIn' import { Migration } from './Migration' -import { ServerUser } from './ServerUser' import { Transaction } from './Transaction' import { TransactionLink } from './TransactionLink' import { User } from './User' @@ -13,7 +12,6 @@ export const entities = [ LoginElopageBuys, LoginEmailOptIn, Migration, - ServerUser, Transaction, TransactionLink, User, From c9701574f38cf28d3ad26a3a37b02dd0bafa82df Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Wed, 20 Apr 2022 11:44:34 +0200 Subject: [PATCH 08/13] seed with isAdmin, test user resolver with isAdmin --- .../src/graphql/resolver/UserResolver.test.ts | 1 + backend/src/seeds/factory/user.ts | 23 ++++--------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index 07b8e59e2..b9e230afe 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -100,6 +100,7 @@ describe('UserResolver', () => { emailChecked: false, passphrase: expect.any(String), language: 'de', + isAdmin: false, deletedAt: null, publisherId: 1234, referrerId: null, diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index ff4c1d6c9..373a3da4f 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -1,7 +1,6 @@ import { createUser, setPassword } from '@/seeds/graphql/mutations' import { User } from '@entity/User' import { LoginEmailOptIn } from '@entity/LoginEmailOptIn' -import { ServerUser } from '@entity/ServerUser' import { UserInterface } from '@/seeds/users/UserInterface' import { ApolloServerTestClient } from 'apollo-server-testing' @@ -29,23 +28,9 @@ export const userFactory = async ( // get user from database const dbUser = await User.findOneOrFail({ id }) - if (user.createdAt || user.deletedAt) { - if (user.createdAt) dbUser.createdAt = user.createdAt - if (user.deletedAt) dbUser.deletedAt = user.deletedAt - await dbUser.save() - } - - if (user.isAdmin) { - const admin = new ServerUser() - admin.username = dbUser.firstName - admin.password = 'please_refactor' - admin.email = dbUser.email - admin.role = 'admin' - admin.activated = 1 - admin.lastLogin = new Date() - admin.created = dbUser.createdAt - admin.modified = dbUser.createdAt - await admin.save() - } + if (user.createdAt) dbUser.createdAt = user.createdAt + if (user.deletedAt) dbUser.deletedAt = user.deletedAt + if (user.isAdmin) dbUser.isAdmin = user.isAdmin + await dbUser.save() } } From 863ecfa4746c4f4f3a0bf5127119e0cce7251520 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 10:58:22 +0200 Subject: [PATCH 09/13] migrate is_admin column in users as datetime with default null --- database/migrations/0034-drop_server_user_table.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts index 914c75457..fd5a9a682 100644 --- a/database/migrations/0034-drop_server_user_table.ts +++ b/database/migrations/0034-drop_server_user_table.ts @@ -5,10 +5,10 @@ add isAdmin COLUMN to users TABLE */ /* eslint-disable @typescript-eslint/no-explicit-any */ export async function upgrade(queryFn: (query: string, values?: any[]) => Promise>) { - await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` boolean DEFAULT false AFTER `language`;') + await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` datetime DEFAULT NULL AFTER `language`;') await queryFn( - 'UPDATE `users` SET `is_admin` = true WHERE `email` IN (SELECT `email` FROM `server_users`);', + 'UPDATE `users` AS `users`, (SELECT * FROM `server_users`) AS `server_users` SET users.`is_admin` = server_users.`modified` WHERE users.`email` IN (SELECT email from `server_users`);', ) await queryFn('DROP TABLE `server_users`;') @@ -30,7 +30,7 @@ export async function downgrade(queryFn: (query: string, values?: any[]) => Prom ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`) await queryFn( - 'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `created`, `created` FROM `users` WHERE `is_admin` = true;', + 'INSERT INTO `server_users` (`email`, `username`, `password`, `created`, `modified`) SELECT `email`, `first_name`, `password`, `is_admin`, `is_admin` FROM `users` WHERE `is_admin` IS NOT NULL;', ) await queryFn('ALTER TABLE `users` DROP COLUMN `is_admin`;') From ba648c67a98dd31c1bf07f494ae47529399a7364 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 11:01:23 +0200 Subject: [PATCH 10/13] entity model users.isAdmin as nullable date --- database/entity/0034-drop_server_user_table/User.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/entity/0034-drop_server_user_table/User.ts b/database/entity/0034-drop_server_user_table/User.ts index 9192b92ff..1f56d13d2 100644 --- a/database/entity/0034-drop_server_user_table/User.ts +++ b/database/entity/0034-drop_server_user_table/User.ts @@ -58,8 +58,8 @@ export class User extends BaseEntity { @Column({ length: 4, default: 'de', collation: 'utf8mb4_unicode_ci', nullable: false }) language: string - @Column({ name: 'is_admin', type: 'bool', nullable: false, default: false }) - isAdmin: boolean + @Column({ name: 'is_admin', type: 'datetime', nullable: true, default: null }) + isAdmin: Date | null @Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null }) referrerId?: number | null From fa6fbe38c8e0ba1b44592146602cfb3938d9242d Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 11:05:51 +0200 Subject: [PATCH 11/13] User.isAdmin as nullable Date --- backend/src/graphql/model/User.ts | 4 ++-- backend/src/seeds/factory/user.ts | 2 +- backend/src/util/communityUser.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/graphql/model/User.ts b/backend/src/graphql/model/User.ts index 1949592c0..4f577f60a 100644 --- a/backend/src/graphql/model/User.ts +++ b/backend/src/graphql/model/User.ts @@ -58,8 +58,8 @@ export class User { // `passphrase` text COLLATE utf8mb4_unicode_ci DEFAULT NULL, - @Field(() => Boolean) - isAdmin: boolean + @Field(() => Date, { nullable: true }) + isAdmin: Date | null // TODO this is a bit inconsistent with what we query from the database // therefore all those fields are now nullable with default value null diff --git a/backend/src/seeds/factory/user.ts b/backend/src/seeds/factory/user.ts index 373a3da4f..4b5913d48 100644 --- a/backend/src/seeds/factory/user.ts +++ b/backend/src/seeds/factory/user.ts @@ -30,7 +30,7 @@ export const userFactory = async ( if (user.createdAt) dbUser.createdAt = user.createdAt if (user.deletedAt) dbUser.deletedAt = user.deletedAt - if (user.isAdmin) dbUser.isAdmin = user.isAdmin + if (user.isAdmin) dbUser.isAdmin = new Date() await dbUser.save() } } diff --git a/backend/src/util/communityUser.ts b/backend/src/util/communityUser.ts index 8b75b37a1..0d0d12f6c 100644 --- a/backend/src/util/communityUser.ts +++ b/backend/src/util/communityUser.ts @@ -17,7 +17,7 @@ const communityDbUser: dbUser = { createdAt: new Date(), emailChecked: false, language: '', - isAdmin: false, + isAdmin: null, publisherId: 0, passphrase: '', settings: [], From 50bddf49e4a1de785e5e413999c6c69186de2d84 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 21 Apr 2022 11:16:14 +0200 Subject: [PATCH 12/13] fix test for User.isAdmin as nullable date --- backend/src/graphql/resolver/UserResolver.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/graphql/resolver/UserResolver.test.ts b/backend/src/graphql/resolver/UserResolver.test.ts index b9e230afe..8e5cb299d 100644 --- a/backend/src/graphql/resolver/UserResolver.test.ts +++ b/backend/src/graphql/resolver/UserResolver.test.ts @@ -100,7 +100,7 @@ describe('UserResolver', () => { emailChecked: false, passphrase: expect.any(String), language: 'de', - isAdmin: false, + isAdmin: null, deletedAt: null, publisherId: 1234, referrerId: null, @@ -337,7 +337,7 @@ describe('UserResolver', () => { firstName: 'Bibi', hasElopage: false, id: expect.any(Number), - isAdmin: false, + isAdmin: null, klickTipp: { newsletterState: false, }, From fdb64071d6cff9a65198594e20b554ed01dc8616 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 25 Apr 2022 11:32:26 +0200 Subject: [PATCH 13/13] join tables to update is_admin --- database/migrations/0034-drop_server_user_table.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/0034-drop_server_user_table.ts b/database/migrations/0034-drop_server_user_table.ts index fd5a9a682..be6b44489 100644 --- a/database/migrations/0034-drop_server_user_table.ts +++ b/database/migrations/0034-drop_server_user_table.ts @@ -8,7 +8,7 @@ export async function upgrade(queryFn: (query: string, values?: any[]) => Promis await queryFn('ALTER TABLE `users` ADD COLUMN `is_admin` datetime DEFAULT NULL AFTER `language`;') await queryFn( - 'UPDATE `users` AS `users`, (SELECT * FROM `server_users`) AS `server_users` SET users.`is_admin` = server_users.`modified` WHERE users.`email` IN (SELECT email from `server_users`);', + 'UPDATE users AS users INNER JOIN server_users AS server_users ON users.email = server_users.email SET users.is_admin = server_users.modified WHERE users.email IN (SELECT email from server_users);', ) await queryFn('DROP TABLE `server_users`;')