UNPKG

next-auth-oauth

Version:

`next-auth-oauth` 是一个基于 Next.js 和 NextAuth 的增强插件,用于简化和增强授权登录的处理。该插件提供了丰富的功能,包括第三方登录绑定、账户管理等,使得授权流程更加高效和灵活。

271 lines 9.81 kB
// 账号注册 import NextAuth, { CredentialsSignin, } from 'next-auth'; import credentials from 'next-auth/providers/credentials'; import { cookies } from 'next/headers'; async function cleanBindAccountInfo() { const cookie = await cookies(); cookie.delete('nextauth.bind.account'); cookie.delete('nextauth.bind.user'); } /** * 从cookie获得绑定账号信息 * @returns */ export async function loadTempOauthUser() { const cookie = await cookies(); try { const account = JSON.parse(cookie.get('nextauth.bind.account')?.value ?? 'null'); const user = JSON.parse(cookie.get('nextauth.bind.user')?.value ?? 'null'); const bindAccount = !!(account && user); return { user, bindAccount, account }; } catch (_error) { return { user: null, bindAccount: false, account: null }; } } export class CredentialsOauth { userService; authAdapter; bindPage; autoBind; constructor(userService, nextAuthAdapter, /** * 配置绑定UI */ bindPage = '/auth/bind', /** * 登录过的账号自动绑定 */ autoBind = false) { this.userService = userService; this.authAdapter = nextAuthAdapter; this.bindPage = bindPage; this.autoBind = autoBind; } /** * 构建账号密码登录的provider * @param options * @returns */ getCredentialsProvider() { return credentials({ credentials: { username: { placeholder: '登录账号' }, password: { placeholder: '密码/验证码', }, type: { name: 'type', label: '登录方式', type: 'radio', value: 'password', children: '密码登录', }, type2: { label: '', name: 'type', type: 'radio', value: 'mobile', children: '手机验证码', }, autoBindTempAccount: { type: 'hidden' }, }, authorize: async (credentials) => { if (typeof credentials.username === 'string' && typeof credentials.password === 'string') { const { bindAccount, account } = await loadTempOauthUser(); const autoBindAccount = credentials['autoBindTempAccount'] == 'string' && credentials['autoBindTempAccount'] === 'true'; const user = await this.userService.login(credentials.username, credentials.password, (credentials.type ?? 'password')); if (autoBindAccount && user && bindAccount && account) { await this.authAdapter.linkAccount?.({ ...account, userId: user.id, type: (account.type ?? 'oauth'), }); cleanBindAccountInfo(); } return user; } throw new CredentialsSignin('账号或者密码错误'); }, }); } async signInCallback(params, auth) { const authUser = await auth(); const { user, account } = params; if (account?.type !== 'oauth' && account?.type !== 'oidc') { return true; } if (account) { const databseUser = await this.authAdapter.getUserByAccount?.({ provider: account.provider, providerAccountId: account.providerAccountId, }); if (databseUser) { return true; } if (authUser?.user?.id && this.autoBind) { await this.authAdapter.linkAccount?.({ ...account, userId: authUser.user.id, type: account?.type, }); return true; } } const cookie = await cookies(); cookie.set('nextauth.bind.account', JSON.stringify(account)); cookie.set('nextauth.bind.user', JSON.stringify(user)); return this.bindPage; } async sessionCallback(params) { const { session, token, user } = params; const newSession = { ...session, user: { ...session.user, name: token.name ?? user?.name, id: user?.id ?? token?.sub, }, }; return newSession; } async jwtCallback(params) { const { token, user, trigger } = params; if (trigger === 'signIn') { // @ts-expect-error token is JWT token.name = user?.nickname ?? user?.name; token.sub = user?.id; token.email = user?.email; token.picture = user?.image; } return token; } /** * * @param config * @returns */ nextAuth(config) { const nextAuthInstance = NextAuth({ ...config, providers: (config.providers ?? []).concat(this.getCredentialsProvider()), callbacks: { signIn: async (params) => { const reuslt = await this.signInCallback(params, nextAuthInstance.auth); if (reuslt === true && typeof config.callbacks?.signIn === 'function') { return config.callbacks?.signIn(params); } return reuslt; }, session: async (params) => { const session = await this.sessionCallback(params); if (typeof config.callbacks?.session === 'function') { return config.callbacks?.session({ ...params, session }); } return session; }, jwt: async (params) => { const token = await this.jwtCallback(params); if (typeof config.callbacks?.jwt === 'function') { return config.callbacks?.jwt({ ...params, token }); } return token; }, }, }); const oauthProviders = config.providers ?.map((provider) => { if (typeof provider === 'function') { provider = provider(); } return { id: provider.id, name: provider.name, // @ts-expect-error provider.type is undefined style: provider.style, }; }) .filter((provider) => provider.id !== 'credentials'); /** * 账号注册,并自动绑定 * 注意这是一个ServerAction * @param formData */ const signUp = async (formData) => { const { user, bindAccount, account } = await loadTempOauthUser(); // 获得账号密码 const { username, autoBindTempAccount, password, redirectTo, ...formUser } = // @ts-expect-error formData is FormData Object.fromEntries(formData); // 创建账号 const adapterUser = await this.userService.registUser({ username: username.toString(), password: password.toString(), formData: formUser, }); if (autoBindTempAccount === 'true' && bindAccount && account && user && adapterUser) { await this.authAdapter.linkAccount?.({ ...account, userId: adapterUser.id, type: 'oauth', }); cleanBindAccountInfo(); return nextAuthInstance.signIn('credentials', { username, password, redirectTo: redirectTo?.toString(), }); } }; const signInAndBindAccount = async (options, params) => { options?.append('autoBindTempAccount', 'true'); return nextAuthInstance.signIn('credentials', options, params); }; const signUpAndBindAccount = async (formData) => { formData?.append('autoBindTempAccount', 'true'); return signUp(formData); }; /** * 列出绑定的授权账户列表 * @returns */ const listAccount = async () => { const session = await nextAuthInstance.auth(); const userId = session?.user?.id; if (userId) { return this.userService.listAccount(userId); } return []; }; return { ...nextAuthInstance, oauthProviders, signUpAndBindAccount: signUpAndBindAccount.bind(this), listAccount: listAccount.bind(this), signUp, signInAndBindAccount, tempOauthUser: loadTempOauthUser, }; } } /** * 封装好的支持授权绑定的服务 * 1. 分装好regist注册`ServerAction` * 2. 封装好`Credentials`的认证逻辑 * 3. 分装好`OauthCallcak`的逻辑,自动判断账号有效性 * @param config * @returns */ export function AdavanceNextAuth(config) { const { bindPage, userService, autoBind, ...nextAuthConfig } = config; const credentialsProvider = new CredentialsOauth(userService, config.adapter, bindPage, autoBind); return credentialsProvider.nextAuth(nextAuthConfig); } //# sourceMappingURL=core.js.map