mirror of
https://gitea.tendokyu.moe/sk1982/actaeon.git
synced 2026-03-21 17:54:19 -05:00
107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import NextAuth from 'next-auth';
|
|
import CredentialsProvider from 'next-auth/providers/credentials';
|
|
import { db, GeneratedDB } from '@/db';
|
|
import bcrypt from 'bcrypt';
|
|
import { DBUserPayload } from '@/types/user';
|
|
import { cache } from 'react';
|
|
import { SelectQueryBuilder, sql } from 'kysely';
|
|
import { AimeUser } from '@/types/db';
|
|
import crypto from 'crypto';
|
|
|
|
let basePath = process.env.BASE_PATH ?? '';
|
|
if (basePath.endsWith('/')) basePath = basePath.slice(0, -1);
|
|
|
|
const selectUserProps = (builder: SelectQueryBuilder<GeneratedDB & { u: AimeUser }, 'u', {}>) => builder.leftJoin(
|
|
eb => eb.selectFrom('chuni_profile_data as chuni')
|
|
.where(({ eb, selectFrom }) => eb('chuni.version', '=', selectFrom('chuni_static_music')
|
|
.select(({ fn }) => fn.max('version').as('latest'))))
|
|
.selectAll()
|
|
.as('chuni'),
|
|
join => join.onRef('chuni.user', '=', 'u.id'))
|
|
.leftJoin('actaeon_user_ext as ext', 'ext.userId', 'u.id')
|
|
.select(({ fn }) => [
|
|
'u.username', 'u.password', 'u.id', 'u.email', 'u.permissions', 'u.created_date', 'u.last_login_date',
|
|
'u.suspend_expire_time',
|
|
'ext.uuid',
|
|
'ext.visibility',
|
|
'ext.homepage',
|
|
'ext.team',
|
|
fn<boolean>('not isnull', ['chuni.id']).as('chuni')
|
|
])
|
|
.executeTakeFirst();
|
|
|
|
const nextAuth = NextAuth({
|
|
pages: {
|
|
signIn: `${basePath}/auth/login`
|
|
},
|
|
basePath: `${basePath}/api/auth/`,
|
|
session: {
|
|
strategy: 'jwt'
|
|
},
|
|
trustHost: true,
|
|
callbacks: {
|
|
async jwt({ token, user }) {
|
|
token.user ??= user;
|
|
const dbUser = await selectUserProps(db.selectFrom('aime_user as u')
|
|
.where('u.id', '=', (token.user as any).id));
|
|
|
|
if (dbUser) {
|
|
const { password, ...payload } = dbUser;
|
|
token.user = { ...(token.user as any), ...payload };
|
|
}
|
|
|
|
return token;
|
|
},
|
|
session({ session, token, user }) {
|
|
session.user = { ...session.user, ...(token.user as any) };
|
|
return session;
|
|
},
|
|
async signIn({ user }) {
|
|
if ((user as any).visibility === null) {
|
|
const uuid = crypto.randomUUID();
|
|
await db.insertInto('actaeon_user_ext')
|
|
.values({
|
|
userId: (user as any).id,
|
|
uuid,
|
|
visibility: 0
|
|
})
|
|
.executeTakeFirst();
|
|
(user as any).uuid = uuid;
|
|
(user as any).visibility = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
},
|
|
providers: [CredentialsProvider({
|
|
name: 'Credentials',
|
|
credentials: {
|
|
username: { label: 'Username', type: 'text', placeholder: 'Username' },
|
|
password: { label: 'Password', type: 'password' }
|
|
},
|
|
async authorize({ username, password }, req) {
|
|
if (typeof username !== 'string' || typeof password !== 'string')
|
|
return null;
|
|
|
|
const user = await selectUserProps(db.selectFrom('aime_user as u')
|
|
.where(({ eb, fn }) =>
|
|
eb(fn<string>('lower', ['u.username']), '=', username.toLowerCase().trim())));
|
|
|
|
if (!user?.password || !await bcrypt.compare(password.trim(), user.password))
|
|
return null;
|
|
|
|
const { password: _, ...payload } = user satisfies { [K in keyof DBUserPayload]: DBUserPayload[K] | null };
|
|
|
|
return payload as any;
|
|
}
|
|
})]
|
|
});
|
|
|
|
export const auth = cache(nextAuth.auth);
|
|
|
|
export const {
|
|
handlers: { GET, POST },
|
|
signIn,
|
|
signOut
|
|
} = nextAuth;
|