next-auth-oauth
Version:
`next-auth-oauth` 是一个基于 Next.js 和 NextAuth 的增强插件,用于简化和增强授权登录的处理。该插件提供了丰富的功能,包括第三方登录绑定、账户管理等,使得授权流程更加高效和灵活。
271 lines • 9.81 kB
JavaScript
// 账号注册
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