mirror of
https://github.com/ajnart/homarr.git
synced 2025-10-26 08:06:12 +01:00
chore: add logs for credentials login
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user