From 5ded128263398f710ec927978fa24b59a78ee0ba Mon Sep 17 00:00:00 2001 From: Jayvin Hernandez Date: Tue, 4 Apr 2023 20:07:41 -0700 Subject: [PATCH] fix: user uuid (#355) * fix: user uuid is used instead of user id for its uniqueness * fix: use cuid instead & exclude from parser * fix: apply new foreign key constraints to existing data * fix: migration partly done * not-fix: General form of migration achieved, still broken * fix: migrate and use db's uuid function for existing users * fix: Proper not nulling! * fix: #354 * fix: migration & use uuid instead --------- Co-authored-by: dicedtomato <35403473+diced@users.noreply.github.com> Co-authored-by: diced --- .../20230405024416_user_uuid/migration.sql | 53 +++++++++++++++++++ prisma/schema.prisma | 39 +++++++------- src/lib/middleware/withOAuth.ts | 6 +-- src/lib/middleware/withZipline.ts | 12 ++--- src/lib/utils/parser.ts | 2 +- src/pages/api/auth/login.ts | 2 +- src/pages/oauth_error.tsx | 5 +- src/server/decorators/postUrl.ts | 2 + 8 files changed, 89 insertions(+), 32 deletions(-) create mode 100644 prisma/migrations/20230405024416_user_uuid/migration.sql diff --git a/prisma/migrations/20230405024416_user_uuid/migration.sql b/prisma/migrations/20230405024416_user_uuid/migration.sql new file mode 100644 index 0000000..40bd40d --- /dev/null +++ b/prisma/migrations/20230405024416_user_uuid/migration.sql @@ -0,0 +1,53 @@ +/* + Warnings: + + - A unique constraint covering the columns `[uuid]` on the table `User` will be added. If there are existing duplicate values, this will fail. + +*/ +-- PRISMA GENERATED BELOW +-- -- DropForeignKey +-- ALTER TABLE "OAuth" DROP CONSTRAINT "OAuth_userId_fkey"; +-- +-- -- AlterTable +-- ALTER TABLE "OAuth" ALTER COLUMN "userId" SET DATA TYPE TEXT; +-- +-- -- AlterTable +-- ALTER TABLE "User" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid(); +-- +-- -- CreateIndex +-- CREATE UNIQUE INDEX "User_uuid_key" ON "User"("uuid"); +-- +-- -- AddForeignKey +-- ALTER TABLE "OAuth" ADD CONSTRAINT "OAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- User made changes below + +-- Rename old foreign key +ALTER TABLE "OAuth" RENAME CONSTRAINT "OAuth_userId_fkey" TO "OAuth_userId_old_fkey"; + +-- Rename old column +ALTER TABLE "OAuth" RENAME COLUMN "userId" TO "userId_old"; + +-- Add new column +ALTER TABLE "OAuth" ADD COLUMN "userId" UUID; + +-- Add user uuid +ALTER TABLE "User" ADD COLUMN "uuid" UUID NOT NULL DEFAULT gen_random_uuid(); + +-- Update table "OAuth" with uuid +UPDATE "OAuth" SET "userId" = "User"."uuid" FROM "User" WHERE "OAuth"."userId_old" = "User"."id"; + +-- Alter table "OAuth" to make "userId" required +ALTER TABLE "OAuth" ALTER COLUMN "userId" SET NOT NULL; + +-- Create index +CREATE UNIQUE INDEX "User_uuid_key" ON "User"("uuid"); + +-- Add new foreign key +ALTER TABLE "OAuth" ADD CONSTRAINT "OAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- Drop old foreign key +ALTER TABLE "OAuth" DROP CONSTRAINT "OAuth_userId_old_fkey"; + +-- Drop old column +ALTER TABLE "OAuth" DROP COLUMN "userId_old"; \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7dad66b..49789b5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,23 +8,24 @@ generator client { } model User { - id Int @id @default(autoincrement()) - username String - password String? - avatar String? - token String - administrator Boolean @default(false) - superAdmin Boolean @default(false) - systemTheme String @default("system") - embed Json @default("{}") - ratelimit DateTime? - totpSecret String? - domains String[] - oauth OAuth[] - files File[] - urls Url[] - Invite Invite[] - Folder Folder[] + id Int @id @default(autoincrement()) + uuid String @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid + username String + password String? + avatar String? + token String + administrator Boolean @default(false) + superAdmin Boolean @default(false) + systemTheme String @default("system") + embed Json @default("{}") + ratelimit DateTime? + totpSecret String? + domains String[] + oauth OAuth[] + files File[] + urls Url[] + Invite Invite[] + Folder Folder[] IncompleteFile IncompleteFile[] } @@ -112,8 +113,8 @@ model Invite { model OAuth { id Int @id @default(autoincrement()) provider OauthProviders - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - userId Int + user User @relation(fields: [userId], references: [uuid], onDelete: Cascade) + userId String username String oauthId String? token String diff --git a/src/lib/middleware/withOAuth.ts b/src/lib/middleware/withOAuth.ts index cb26433..11a131b 100644 --- a/src/lib/middleware/withOAuth.ts +++ b/src/lib/middleware/withOAuth.ts @@ -135,7 +135,7 @@ export const withOAuth = } else throw e; } - res.setUserCookie(user.id); + res.setUserCookie(user.uuid); logger.info(`User ${user.username} (${user.id}) linked account via oauth(${provider})`); return res.redirect('/'); @@ -153,7 +153,7 @@ export const withOAuth = }, }); - res.setUserCookie(user.id); + res.setUserCookie(user.uuid); logger.info(`User ${user.username} (${user.id}) logged in via oauth(${provider})`); return res.redirect('/dashboard'); @@ -203,7 +203,7 @@ export const withOAuth = logger.debug(`created user ${JSON.stringify(nuser)} via oauth(${provider})`); logger.info(`Created user ${nuser.username} via oauth(${provider})`); - res.setUserCookie(nuser.id); + res.setUserCookie(nuser.uuid); logger.info(`User ${nuser.username} (${nuser.id}) logged in via oauth(${provider})`); return res.redirect('/dashboard'); diff --git a/src/lib/middleware/withZipline.ts b/src/lib/middleware/withZipline.ts index 826aef1..11d0120 100644 --- a/src/lib/middleware/withZipline.ts +++ b/src/lib/middleware/withZipline.ts @@ -54,7 +54,7 @@ export type NextApiRes = NextApiResponse & NextApiResExtraObj & { json: (json: Record, status?: number) => void; setCookie: (name: string, value: unknown, options: CookieSerializeOptions) => void; - setUserCookie: (id: number) => void; + setUserCookie: (id: string) => void; }; export type ZiplineApiConfig = { @@ -184,7 +184,7 @@ export const withZipline = const user = await prisma.user.findFirst({ where: { - id: Number(userId), + uuid: userId, }, include: { oauth: true, @@ -202,22 +202,22 @@ export const withZipline = } }; - res.setCookie = (name: string, value: unknown, options: CookieSerializeOptions = {}) => { + res.setCookie = (name: string, value: string, options: CookieSerializeOptions = {}) => { if ('maxAge' in options) { options.expires = new Date(Date.now() + options.maxAge * 1000); options.maxAge /= 1000; } - const signed = sign64(String(value), config.core.secret); + const signed = sign64(value, config.core.secret); Logger.get('api').debug(`headers(${JSON.stringify(req.headers)}): cookie(${name}, ${value})`); res.setHeader('Set-Cookie', serialize(name, signed, options)); }; - res.setUserCookie = (id: number) => { + res.setUserCookie = (id: string) => { req.cleanCookie('user'); - res.setCookie('user', String(id), { + res.setCookie('user', id, { sameSite: 'lax', expires: new Date(Date.now() + 6.048e8 * 2), path: '/', diff --git a/src/lib/utils/parser.ts b/src/lib/utils/parser.ts index b1f2666..ca3993e 100644 --- a/src/lib/utils/parser.ts +++ b/src/lib/utils/parser.ts @@ -27,7 +27,7 @@ export function parseString(str: string, value: ParseValue) { continue; } - if (['password', 'avatar'].includes(matches.groups.prop)) { + if (['password', 'avatar', 'uuid'].includes(matches.groups.prop)) { str = replaceCharsFromString(str, '{unknown_property}', matches.index, re.lastIndex); re.lastIndex = matches.index; continue; diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts index 1b085bd..2e2668b 100644 --- a/src/pages/api/auth/login.ts +++ b/src/pages/api/auth/login.ts @@ -56,7 +56,7 @@ async function handler(req: NextApiReq, res: NextApiRes) { if (!success) return res.badRequest('Invalid code', { totp: true }); } - res.setUserCookie(user.id); + res.setUserCookie(user.uuid); logger.info(`User ${user.username} (${user.id}) logged in`); return res.json({ success: true }); diff --git a/src/pages/oauth_error.tsx b/src/pages/oauth_error.tsx index 92c8ac6..6eae9eb 100644 --- a/src/pages/oauth_error.tsx +++ b/src/pages/oauth_error.tsx @@ -12,7 +12,8 @@ export default function OauthError({ error, provider }) { useEffect(() => { const interval = setInterval(() => { - setRemaining((remaining) => remaining - 1); + if (remaining > 0) setRemaining((remaining) => remaining - 1); + else clearInterval(interval); }, 1000); return () => clearInterval(interval); @@ -43,7 +44,7 @@ export default function OauthError({ error, provider }) { {error} - Redirecting to login in {remaining} second{remaining === 1 ? 's' : ''} + Redirecting to login in {remaining} second{remaining !== 1 ? 's' : ''}