feat: add ldap and oidc support (#1497)
Co-authored-by: Thomas Camlong <49837342+ajnart@users.noreply.github.com> Co-authored-by: Tagaishi <Tagaishi@hotmail.ch>
This commit is contained in:
@@ -1,57 +1,17 @@
|
||||
import { DrizzleAdapter } from '@auth/drizzle-adapter';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import Consola from 'consola';
|
||||
import Cookies from 'cookies';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { type GetServerSidePropsContext, type NextApiRequest, type NextApiResponse } from 'next';
|
||||
import { type DefaultSession, type NextAuthOptions, getServerSession } from 'next-auth';
|
||||
import { type NextAuthOptions, getServerSession } from 'next-auth';
|
||||
import { Adapter } from 'next-auth/adapters';
|
||||
import { decode, encode } from 'next-auth/jwt';
|
||||
import Credentials from 'next-auth/providers/credentials';
|
||||
import { adapter, onCreateUser, providers } from '~/utils/auth';
|
||||
import EmptyNextAuthProvider from '~/utils/empty-provider';
|
||||
import { fromDate, generateSessionToken } from '~/utils/session';
|
||||
import { colorSchemeParser, signInSchema } from '~/validations/user';
|
||||
import { colorSchemeParser } from '~/validations/user';
|
||||
|
||||
import { db } from './db';
|
||||
import { users } from './db/schema';
|
||||
|
||||
/**
|
||||
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
|
||||
* object and keep type safety.
|
||||
*
|
||||
* @see https://next-auth.js.org/getting-started/typescript#module-augmentation
|
||||
*/
|
||||
declare module 'next-auth' {
|
||||
interface Session extends DefaultSession {
|
||||
user: DefaultSession['user'] & {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
colorScheme: 'light' | 'dark' | 'environment';
|
||||
autoFocusSearch: boolean;
|
||||
language: string;
|
||||
// ...other properties
|
||||
// role: UserRole;
|
||||
};
|
||||
}
|
||||
|
||||
interface User {
|
||||
isAdmin: boolean;
|
||||
colorScheme: 'light' | 'dark' | 'environment';
|
||||
autoFocusSearch: boolean;
|
||||
language: string;
|
||||
// ...other properties
|
||||
// role: UserRole;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'next-auth/jwt' {
|
||||
interface JWT {
|
||||
id: string;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
const adapter = DrizzleAdapter(db);
|
||||
const sessionMaxAgeInSeconds = 30 * 24 * 60 * 60; // 30 days
|
||||
|
||||
/**
|
||||
@@ -63,6 +23,9 @@ export const constructAuthOptions = (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): NextAuthOptions => ({
|
||||
events: {
|
||||
createUser: onCreateUser,
|
||||
},
|
||||
callbacks: {
|
||||
async session({ session, user }) {
|
||||
if (session.user) {
|
||||
@@ -133,58 +96,7 @@ export const constructAuthOptions = (
|
||||
error: '/auth/login',
|
||||
},
|
||||
adapter: adapter as Adapter,
|
||||
providers: [
|
||||
Credentials({
|
||||
name: 'credentials',
|
||||
credentials: {
|
||||
name: {
|
||||
label: 'Username',
|
||||
type: 'text',
|
||||
},
|
||||
password: { label: 'Password', type: 'password' },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
const data = await signInSchema.parseAsync(credentials);
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
with: {
|
||||
settings: {
|
||||
columns: {
|
||||
colorScheme: true,
|
||||
language: true,
|
||||
autoFocusSearch: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: eq(users.name, data.name),
|
||||
});
|
||||
|
||||
if (!user || !user.password) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Consola.log(`user ${user.name} is trying to log in. checking password...`);
|
||||
const isValidPassword = await bcrypt.compare(data.password, user.password);
|
||||
|
||||
if (!isValidPassword) {
|
||||
Consola.log(`password for user ${user.name} was incorrect`);
|
||||
return null;
|
||||
}
|
||||
|
||||
Consola.log(`user ${user.name} successfully authorized`);
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
isAdmin: false,
|
||||
colorScheme: colorSchemeParser.parse(user.settings?.colorScheme),
|
||||
language: user.settings?.language ?? 'en',
|
||||
autoFocusSearch: user.settings?.autoFocusSearch ?? false,
|
||||
};
|
||||
},
|
||||
}),
|
||||
EmptyNextAuthProvider(),
|
||||
],
|
||||
providers: [...providers, EmptyNextAuthProvider()],
|
||||
jwt: {
|
||||
async encode(params) {
|
||||
if (!isCredentialsRequest(req)) {
|
||||
@@ -207,10 +119,12 @@ export const constructAuthOptions = (
|
||||
});
|
||||
|
||||
const isCredentialsRequest = (req: NextApiRequest): boolean => {
|
||||
const nextAuthQueryParams = req.query.nextauth as ['callback', 'credentials'];
|
||||
const nextAuthQueryParams = req.query.nextauth as string[];
|
||||
return (
|
||||
nextAuthQueryParams.includes('callback') &&
|
||||
nextAuthQueryParams.includes('credentials') &&
|
||||
(nextAuthQueryParams.includes('credentials') ||
|
||||
nextAuthQueryParams.includes('ldap') ||
|
||||
nextAuthQueryParams.includes('oidc')) &&
|
||||
req.method === 'POST'
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,9 @@ import { InferSelectModel, relations } from 'drizzle-orm';
|
||||
import { index, int, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { type AdapterAccount } from 'next-auth/adapters';
|
||||
|
||||
export const users = sqliteTable('user', {
|
||||
// workaround for typescript check in adapter
|
||||
// preferably add email into credential login and make email non-nullable here
|
||||
export const _users = {
|
||||
id: text('id').notNull().primaryKey(),
|
||||
name: text('name'),
|
||||
email: text('email'),
|
||||
@@ -12,7 +14,9 @@ export const users = sqliteTable('user', {
|
||||
salt: text('salt'),
|
||||
isAdmin: int('is_admin', { mode: 'boolean' }).notNull().default(false),
|
||||
isOwner: int('is_owner', { mode: 'boolean' }).notNull().default(false),
|
||||
});
|
||||
};
|
||||
|
||||
export const users = sqliteTable('user', _users);
|
||||
|
||||
export const accounts = sqliteTable(
|
||||
'account',
|
||||
|
||||
Reference in New Issue
Block a user