chore: add logs for credentials login

This commit is contained in:
Meier Lukas
2024-10-27 11:00:48 +01:00
parent 5b23f7d13a
commit 0f7e661c31
4 changed files with 90 additions and 49 deletions

View File

@@ -1,7 +1,10 @@
import Consola from 'consola';
import { NextApiRequest, NextApiResponse } from 'next';
import NextAuth from 'next-auth';
import { constructAuthOptions } from '~/server/auth';
export default async function auth(req: NextApiRequest, res: NextApiResponse) {
const sanitizedUrl = req.url?.split('?')[0];
Consola.info(`Authentication endpoint called method=${req.method} url=${sanitizedUrl}`);
return await NextAuth(req, res, await constructAuthOptions(req, res));
}

View File

@@ -1,19 +1,14 @@
import { TRPCError } from '@trpc/server';
import bcrypt from 'bcryptjs';
import Consola from 'consola';
import { randomUUID } from 'crypto';
import { and, eq, like, sql } from 'drizzle-orm';
import { createSelectSchema } from 'drizzle-zod';
import { z } from 'zod';
import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../../../data/constants';
import { adminProcedure, createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
import { PossibleRoleFilter } from '~/pages/manage/users';
import { db } from '~/server/db';
import { getTotalUserCountAsync } from '~/server/db/queries/user';
import { invites, sessions, users, userSettings, UserSettings } from '~/server/db/schema';
import { UserSettings, invites, sessions, userSettings, users } from '~/server/db/schema';
import { hashPassword } from '~/utils/security';
import {
colorSchemeParser,
@@ -21,8 +16,9 @@ import {
signUpFormSchema,
updateSettingsValidationSchema,
} from '~/validations/user';
import { PossibleRoleFilter } from '~/pages/manage/users';
import { createSelectSchema } from 'drizzle-zod';
import { COOKIE_COLOR_SCHEME_KEY, COOKIE_LOCALE_KEY } from '../../../../data/constants';
import { adminProcedure, createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
export const userRouter = createTRPCRouter({
createOwnerAccount: publicProcedure.input(signUpFormSchema).mutation(async ({ ctx, input }) => {
@@ -33,13 +29,17 @@ export const userRouter = createTRPCRouter({
});
}
await createUserIfNotPresent(input, {
Consola.info('Creating owner account');
const creationId = await createUserIfNotPresent(input, {
defaultSettings: {
colorScheme: colorSchemeParser.parse(ctx.cookies[COOKIE_COLOR_SCHEME_KEY]),
language: ctx.cookies[COOKIE_LOCALE_KEY] ?? 'en',
},
isOwner: true,
});
Consola.info(`Owner account created userId=${creationId}`);
}),
updatePassword: adminProcedure
.meta({ openapi: { method: 'PUT', path: '/users/password', tags: ['user'] } })
@@ -48,7 +48,7 @@ export const userRouter = createTRPCRouter({
userId: z.string(),
newPassword: z.string().min(3),
terminateExistingSessions: z.boolean(),
}),
})
)
.output(z.void())
.mutation(async ({ input, ctx }) => {
@@ -96,8 +96,8 @@ export const userRouter = createTRPCRouter({
signUpFormSchema.and(
z.object({
inviteToken: z.string(),
}),
),
})
)
)
.mutation(async ({ ctx, input }) => {
const invite = await db.query.invites.findFirst({
@@ -129,7 +129,7 @@ export const userRouter = createTRPCRouter({
.input(
z.object({
colorScheme: colorSchemeParser,
}),
})
)
.mutation(async ({ ctx, input }) => {
await db
@@ -179,7 +179,7 @@ export const userRouter = createTRPCRouter({
.input(
z.object({
language: z.string(),
}),
})
)
.output(z.void())
.mutation(async ({ ctx, input }) => {
@@ -254,23 +254,26 @@ export const userRouter = createTRPCRouter({
.transform((value) => (value.length > 0 ? value : undefined))
.optional(),
}),
}),
})
)
.output(
z.object({
users: z.array(
z.object({
id: z.string(),
name: z.string(),
email: z.string().or(z.null()).optional(),
isAdmin: z.boolean(),
isOwner: z.boolean(),
})
),
countPages: z.number().min(0),
stats: z.object({
roles: z.record(z.number()),
}),
})
)
.output(z.object({
users: z.array(z.object({
id: z.string(),
name: z.string(),
email: z.string().or(z.null()).optional(),
isAdmin: z.boolean(),
isOwner: z.boolean(),
})),
countPages: z.number().min(0),
stats: z.object({
roles: z.record(z.number()),
}),
}))
.query(async ({ input }) => {
const roleFilter = () => {
if (input.search.role === PossibleRoleFilter[1].id) {
return eq(users.isOwner, true);
@@ -291,13 +294,22 @@ export const userRouter = createTRPCRouter({
const dbUsers = await db.query.users.findMany({
limit: limit + 1,
offset: limit * input.page,
where: and(input.search.fullTextSearch ? like(users.name, `%${input.search.fullTextSearch}%`) : undefined, roleFilter()),
where: and(
input.search.fullTextSearch
? like(users.name, `%${input.search.fullTextSearch}%`)
: undefined,
roleFilter()
),
});
const countUsers = await db
.select({ count: sql<number>`count(*)` })
.from(users)
.where(input.search.fullTextSearch ? like(users.name, `%${input.search.fullTextSearch}%`) : undefined)
.where(
input.search.fullTextSearch
? like(users.name, `%${input.search.fullTextSearch}%`)
: undefined
)
.where(roleFilter())
.then((rows) => rows[0].count);
@@ -351,7 +363,8 @@ export const userRouter = createTRPCRouter({
password: true,
salt: true,
})
.optional())
.optional()
)
.query(async ({ input }) => {
return db.query.users.findFirst({
where: eq(users.id, input.userId),
@@ -363,24 +376,32 @@ export const userRouter = createTRPCRouter({
}),
updateDetails: adminProcedure
.meta({ openapi: { method: 'PUT', path: '/users/details', tags: ['user'] } })
.input(z.object({
userId: z.string(),
username: z.string(),
eMail: z.string().optional().transform(value => value?.length === 0 ? null : value),
}))
.input(
z.object({
userId: z.string(),
username: z.string(),
eMail: z
.string()
.optional()
.transform((value) => (value?.length === 0 ? null : value)),
})
)
.output(z.void())
.mutation(async ({ input }) => {
await db.update(users).set({
name: input.username,
email: input.eMail as string | null,
}).where(eq(users.id, input.userId));
await db
.update(users)
.set({
name: input.username,
email: input.eMail as string | null,
})
.where(eq(users.id, input.userId));
}),
deleteUser: adminProcedure
.meta({ openapi: { method: 'DELETE', path: '/users', tags: ['user'] } })
.input(
z.object({
id: z.string(),
}),
})
)
.output(z.void())
.mutation(async ({ ctx, input }) => {
@@ -417,7 +438,7 @@ const createUserIfNotPresent = async (
options: {
defaultSettings?: Partial<UserSettings>;
isOwner?: boolean;
} | void,
} | void
) => {
const existingUser = await db.query.users.findFirst({
where: eq(users.name, input.username),

View File

@@ -1,3 +1,4 @@
import Consola from 'consola';
import Cookies from 'cookies';
import { eq } from 'drizzle-orm';
import { type GetServerSidePropsContext, type NextApiRequest, type NextApiResponse } from 'next';
@@ -62,18 +63,27 @@ export const constructAuthOptions = async (
return session;
},
async signIn({ user }) {
Consola.info('User sign in');
// Check if this sign in callback is being called in the credentials authentication flow.
// If so, use the next-auth adapter to create a session entry in the database
// (SignIn is called after authorize so we can safely assume the user is valid and already authenticated).
if (!isCredentialsRequest(req)) return true;
if (!isCredentialsRequest(req)) {
Consola.error('Not credentials request');
return true;
}
if (!user) return true;
if (!user) {
Consola.error('No user');
return true;
}
const sessionToken = generateSessionToken();
const sessionExpiry = fromDate(sessionMaxAgeInSeconds);
// https://github.com/nextauthjs/next-auth/issues/6106
if (!adapter?.createSession) {
Consola.error('Adapter does not have createSession method');
return false;
}
@@ -88,11 +98,14 @@ export const constructAuthOptions = async (
expires: sessionExpiry,
});
Consola.info('Session created');
return true;
},
async redirect({ url, baseUrl }) {
const pathname = new URL(url, baseUrl).pathname;
const redirectUrl = createRedirectUri(req.headers, pathname);
Consola.info(`Redirecting to ${redirectUrl}`);
return redirectUrl;
},
},

View File

@@ -2,7 +2,7 @@ import bcrypt from 'bcryptjs';
import Consola from 'consola';
import { eq } from 'drizzle-orm';
import Credentials from 'next-auth/providers/credentials';
import { colorSchemeParser, signInSchema } from '~/validations/user';
import { signInSchema } from '~/validations/user';
import { db } from '../../server/db';
import { users } from '../../server/db/schema';
@@ -17,8 +17,11 @@ export default Credentials({
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
Consola.info("Authorizing user's credentials...");
const data = await signInSchema.parseAsync(credentials);
Consola.info(`Checking if user ${data.name} exists...`);
const user = await db.query.users.findFirst({
with: {
settings: {
@@ -33,6 +36,7 @@ export default Credentials({
});
if (!user || !user.password) {
Consola.info(`user ${data.name} does not exist`);
return null;
}