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 <pranaco2@gmail.com>
This commit is contained in:
parent
eedeb89c7d
commit
5ded128263
8 changed files with 89 additions and 32 deletions
53
prisma/migrations/20230405024416_user_uuid/migration.sql
Normal file
53
prisma/migrations/20230405024416_user_uuid/migration.sql
Normal file
|
@ -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";
|
|
@ -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
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -54,7 +54,7 @@ export type NextApiRes = NextApiResponse &
|
|||
NextApiResExtraObj & {
|
||||
json: (json: Record<string, unknown>, 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: '/',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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 }) {
|
|||
</Title>
|
||||
<MutedText sx={{ fontSize: 40, fontWeight: 500 }}>{error}</MutedText>
|
||||
<MutedText>
|
||||
Redirecting to login in {remaining} second{remaining === 1 ? 's' : ''}
|
||||
Redirecting to login in {remaining} second{remaining !== 1 ? 's' : ''}
|
||||
</MutedText>
|
||||
<Button component={Link} href='/dashboard'>
|
||||
Head to the Dashboard
|
||||
|
|
|
@ -7,6 +7,8 @@ function postUrlDecorator(fastify: FastifyInstance, _, done) {
|
|||
done();
|
||||
|
||||
async function postUrl(this: FastifyReply, url: Url) {
|
||||
if (!url) return true;
|
||||
|
||||
const nUrl = await this.server.prisma.url.update({
|
||||
where: {
|
||||
id: url.id,
|
||||
|
|
Loading…
Reference in a new issue