@lobehub/chat 1.122.4 → 1.122.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/packages/utils/src/server/__tests__/auth.test.ts +1 -1
- package/packages/utils/src/server/auth.ts +2 -2
- package/src/app/(backend)/api/auth/adapter/route.ts +137 -0
- package/src/app/(backend)/api/webhooks/logto/route.ts +9 -0
- package/src/config/auth.ts +4 -0
- package/src/libs/next-auth/adapter/index.ts +103 -201
- package/src/libs/next-auth/auth.config.ts +22 -10
- package/src/libs/next-auth/index.ts +11 -24
- package/src/libs/trpc/edge/context.ts +2 -2
- package/src/libs/trpc/lambda/context.ts +2 -2
- package/src/middleware.ts +2 -2
- package/src/server/routers/lambda/user.test.ts +4 -17
- package/src/server/routers/lambda/user.ts +6 -15
- package/src/server/services/nextAuthUser/index.ts +282 -6
- package/packages/database/src/server/models/__tests__/nextauth.test.ts +0 -556
- package/src/libs/next-auth/edge.ts +0 -26
- package/src/server/services/nextAuthUser/index.test.ts +0 -108
- /package/src/{libs/next-auth/adapter → server/services/nextAuthUser}/utils.ts +0 -0
@@ -1,23 +1,32 @@
|
|
1
1
|
import type { NextAuthConfig } from 'next-auth';
|
2
2
|
|
3
|
-
import { authEnv } from '@/config/auth';
|
4
|
-
|
5
3
|
import { ssoProviders } from './sso-providers';
|
4
|
+
import { LobeNextAuthDbAdapter } from './adapter';
|
5
|
+
import { getAuthConfig } from '@/config/auth';
|
6
|
+
|
7
|
+
const {
|
8
|
+
NEXT_AUTH_DEBUG,
|
9
|
+
NEXT_AUTH_SECRET,
|
10
|
+
NEXT_AUTH_SSO_SESSION_STRATEGY,
|
11
|
+
NEXT_AUTH_SSO_PROVIDERS,
|
12
|
+
NEXT_PUBLIC_ENABLE_NEXT_AUTH
|
13
|
+
} = getAuthConfig();
|
6
14
|
|
7
15
|
export const initSSOProviders = () => {
|
8
|
-
return
|
9
|
-
?
|
10
|
-
|
16
|
+
return NEXT_PUBLIC_ENABLE_NEXT_AUTH
|
17
|
+
? NEXT_AUTH_SSO_PROVIDERS.split(/[,,]/).map((provider) => {
|
18
|
+
const validProvider = ssoProviders.find((item) => item.id === provider.trim());
|
11
19
|
|
12
|
-
|
20
|
+
if (validProvider) return validProvider.provider;
|
13
21
|
|
14
|
-
|
15
|
-
|
22
|
+
throw new Error(`[NextAuth] provider ${provider} is not supported`);
|
23
|
+
})
|
16
24
|
: [];
|
17
25
|
};
|
18
26
|
|
19
27
|
// Notice this is only an object, not a full Auth.js instance
|
20
28
|
export default {
|
29
|
+
adapter: LobeNextAuthDbAdapter(),
|
21
30
|
callbacks: {
|
22
31
|
// Note: Data processing order of callback: authorize --> jwt --> session
|
23
32
|
async jwt({ token, user }) {
|
@@ -39,12 +48,15 @@ export default {
|
|
39
48
|
return session;
|
40
49
|
},
|
41
50
|
},
|
42
|
-
debug:
|
51
|
+
debug: NEXT_AUTH_DEBUG,
|
43
52
|
pages: {
|
44
53
|
error: '/next-auth/error',
|
45
54
|
signIn: '/next-auth/signin',
|
46
55
|
},
|
47
56
|
providers: initSSOProviders(),
|
48
|
-
secret:
|
57
|
+
secret: NEXT_AUTH_SECRET,
|
58
|
+
session: {
|
59
|
+
strategy: NEXT_AUTH_SSO_SESSION_STRATEGY,
|
60
|
+
},
|
49
61
|
trustHost: process.env?.AUTH_TRUST_HOST ? process.env.AUTH_TRUST_HOST === 'true' : true,
|
50
62
|
} satisfies NextAuthConfig;
|
@@ -1,33 +1,20 @@
|
|
1
1
|
import NextAuth from 'next-auth';
|
2
2
|
|
3
|
-
import
|
4
|
-
import { serverDB } from '@/database/server';
|
5
|
-
|
6
|
-
import { LobeNextAuthDbAdapter } from './adapter';
|
7
|
-
import config from './auth.config';
|
8
|
-
|
9
|
-
const { NEXT_PUBLIC_ENABLED_SERVER_SERVICE } = getServerDBConfig();
|
3
|
+
import authConfig from './auth.config';
|
10
4
|
|
11
5
|
/**
|
12
|
-
* NextAuth initialization
|
6
|
+
* NextAuth initialization without Database adapter
|
7
|
+
*
|
8
|
+
* @note
|
9
|
+
* We currently use `jwt` strategy for session management.
|
10
|
+
* So you don't need to import `signIn` or `signOut` from
|
11
|
+
* this module, just import from `next-auth` directly.
|
13
12
|
*
|
13
|
+
* Inside react component
|
14
14
|
* @example
|
15
15
|
* ```ts
|
16
|
-
* import
|
17
|
-
*
|
16
|
+
* import { signOut } from 'next-auth/react';
|
17
|
+
* signOut();
|
18
18
|
* ```
|
19
|
-
*
|
20
|
-
* @note
|
21
|
-
* If you meet the edge runtime compatible problem,
|
22
|
-
* you can import from `@/libs/next-auth/edge` which is not initial with the database adapter.
|
23
|
-
*
|
24
|
-
* The difference and usage of the two different NextAuth modules is can be
|
25
|
-
* ref to: https://github.com/lobehub/lobe-chat/pull/2935
|
26
19
|
*/
|
27
|
-
export default NextAuth(
|
28
|
-
...config,
|
29
|
-
adapter: NEXT_PUBLIC_ENABLED_SERVER_SERVICE ? LobeNextAuthDbAdapter(serverDB) : undefined,
|
30
|
-
session: {
|
31
|
-
strategy: 'jwt',
|
32
|
-
},
|
33
|
-
});
|
20
|
+
export default NextAuth(authConfig);
|
@@ -54,9 +54,9 @@ export const createEdgeContext = async (request: NextRequest): Promise<EdgeConte
|
|
54
54
|
|
55
55
|
if (enableNextAuth) {
|
56
56
|
try {
|
57
|
-
const { default:
|
57
|
+
const { default: NextAuth } = await import('@/libs/next-auth');
|
58
58
|
|
59
|
-
const session = await
|
59
|
+
const session = await NextAuth.auth();
|
60
60
|
if (session && session?.user?.id) {
|
61
61
|
auth = session.user;
|
62
62
|
userId = session.user.id;
|
@@ -161,9 +161,9 @@ export const createLambdaContext = async (request: NextRequest): Promise<LambdaC
|
|
161
161
|
if (enableNextAuth) {
|
162
162
|
log('Attempting NextAuth authentication');
|
163
163
|
try {
|
164
|
-
const { default:
|
164
|
+
const { default: NextAuth } = await import('@/libs/next-auth');
|
165
165
|
|
166
|
-
const session = await
|
166
|
+
const session = await NextAuth.auth();
|
167
167
|
if (session && session?.user?.id) {
|
168
168
|
auth = session.user;
|
169
169
|
userId = session.user.id;
|
package/src/middleware.ts
CHANGED
@@ -10,7 +10,7 @@ import { OAUTH_AUTHORIZED } from '@/const/auth';
|
|
10
10
|
import { LOBE_LOCALE_COOKIE } from '@/const/locale';
|
11
11
|
import { LOBE_THEME_APPEARANCE } from '@/const/theme';
|
12
12
|
import { appEnv } from '@/envs/app';
|
13
|
-
import
|
13
|
+
import NextAuth from '@/libs/next-auth';
|
14
14
|
import { Locales } from '@/locales/resources';
|
15
15
|
|
16
16
|
import { oidcEnv } from './envs/oidc';
|
@@ -170,7 +170,7 @@ const isProtectedRoute = createRouteMatcher([
|
|
170
170
|
]);
|
171
171
|
|
172
172
|
// Initialize an Edge compatible NextAuth middleware
|
173
|
-
const nextAuthMiddleware =
|
173
|
+
const nextAuthMiddleware = NextAuth.auth((req) => {
|
174
174
|
logNextAuth('NextAuth middleware processing request: %s %s', req.method, req.url);
|
175
175
|
|
176
176
|
const response = defaultMiddleware(req);
|
@@ -6,8 +6,8 @@ import { MessageModel } from '@/database/models/message';
|
|
6
6
|
import { SessionModel } from '@/database/models/session';
|
7
7
|
import { UserModel, UserNotFoundError } from '@/database/models/user';
|
8
8
|
import { serverDB } from '@/database/server';
|
9
|
-
import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
|
10
9
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
10
|
+
import { NextAuthUserService } from '@/server/services/nextAuthUser';
|
11
11
|
import { UserService } from '@/server/services/user';
|
12
12
|
|
13
13
|
import { userRouter } from './user';
|
@@ -24,10 +24,10 @@ vi.mock('@/database/server', () => ({
|
|
24
24
|
vi.mock('@/database/models/message');
|
25
25
|
vi.mock('@/database/models/session');
|
26
26
|
vi.mock('@/database/models/user');
|
27
|
-
vi.mock('@/libs/next-auth/adapter');
|
28
27
|
vi.mock('@/server/modules/KeyVaultsEncrypt');
|
29
28
|
vi.mock('@/server/modules/S3');
|
30
29
|
vi.mock('@/server/services/user');
|
30
|
+
vi.mock('@/server/services/nextAuthUser');
|
31
31
|
vi.mock('@/const/auth', () => ({
|
32
32
|
enableClerk: true,
|
33
33
|
}));
|
@@ -221,7 +221,7 @@ describe('userRouter', () => {
|
|
221
221
|
type: 'oauth',
|
222
222
|
};
|
223
223
|
|
224
|
-
vi.mocked(
|
224
|
+
vi.mocked(NextAuthUserService).mockReturnValue({
|
225
225
|
getAccount: vi.fn().mockResolvedValue(mockAccount),
|
226
226
|
unlinkAccount: vi.fn().mockResolvedValue(undefined),
|
227
227
|
} as any);
|
@@ -237,7 +237,7 @@ describe('userRouter', () => {
|
|
237
237
|
providerAccountId: '123',
|
238
238
|
};
|
239
239
|
|
240
|
-
vi.mocked(
|
240
|
+
vi.mocked(NextAuthUserService).mockReturnValue({
|
241
241
|
getAccount: vi.fn().mockResolvedValue(null),
|
242
242
|
unlinkAccount: vi.fn(),
|
243
243
|
} as any);
|
@@ -246,19 +246,6 @@ describe('userRouter', () => {
|
|
246
246
|
userRouter.createCaller({ ...mockCtx }).unlinkSSOProvider(mockInput),
|
247
247
|
).rejects.toThrow('The account does not exist');
|
248
248
|
});
|
249
|
-
|
250
|
-
it('should throw error if adapter methods are not implemented', async () => {
|
251
|
-
const mockInput = {
|
252
|
-
provider: 'google',
|
253
|
-
providerAccountId: '123',
|
254
|
-
};
|
255
|
-
|
256
|
-
vi.mocked(LobeNextAuthDbAdapter).mockReturnValue({} as any);
|
257
|
-
|
258
|
-
await expect(
|
259
|
-
userRouter.createCaller({ ...mockCtx }).unlinkSSOProvider(mockInput),
|
260
|
-
).rejects.toThrow('The method in LobeNextAuthDbAdapter `unlinkAccount` is not implemented');
|
261
|
-
});
|
262
249
|
});
|
263
250
|
|
264
251
|
describe('updateSettings', () => {
|
@@ -9,12 +9,12 @@ import { SessionModel } from '@/database/models/session';
|
|
9
9
|
import { UserModel, UserNotFoundError } from '@/database/models/user';
|
10
10
|
import { ClerkAuth } from '@/libs/clerk-auth';
|
11
11
|
import { pino } from '@/libs/logger';
|
12
|
-
import { LobeNextAuthDbAdapter } from '@/libs/next-auth/adapter';
|
13
12
|
import { authedProcedure, router } from '@/libs/trpc/lambda';
|
14
13
|
import { serverDatabase } from '@/libs/trpc/lambda/middleware';
|
15
14
|
import { KeyVaultsGateKeeper } from '@/server/modules/KeyVaultsEncrypt';
|
16
15
|
import { S3 } from '@/server/modules/S3';
|
17
16
|
import { FileService } from '@/server/services/file';
|
17
|
+
import { NextAuthUserService } from '@/server/services/nextAuthUser';
|
18
18
|
import { UserService } from '@/server/services/user';
|
19
19
|
import {
|
20
20
|
NextAuthAccountSchame,
|
@@ -29,7 +29,7 @@ const userProcedure = authedProcedure.use(serverDatabase).use(async ({ ctx, next
|
|
29
29
|
ctx: {
|
30
30
|
clerkAuth: new ClerkAuth(),
|
31
31
|
fileService: new FileService(ctx.serverDB, ctx.userId),
|
32
|
-
|
32
|
+
nextAuthUserService: new NextAuthUserService(ctx.serverDB),
|
33
33
|
userModel: new UserModel(ctx.serverDB, ctx.userId),
|
34
34
|
},
|
35
35
|
});
|
@@ -134,19 +134,10 @@ export const userRouter = router({
|
|
134
134
|
|
135
135
|
unlinkSSOProvider: userProcedure.input(NextAuthAccountSchame).mutation(async ({ ctx, input }) => {
|
136
136
|
const { provider, providerAccountId } = input;
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
typeof ctx.nextAuthDbAdapter.getAccount === 'function'
|
142
|
-
) {
|
143
|
-
const account = await ctx.nextAuthDbAdapter.getAccount(providerAccountId, provider);
|
144
|
-
// The userId can either get from ctx.nextAuth?.id or ctx.userId
|
145
|
-
if (!account || account.userId !== ctx.userId) throw new Error('The account does not exist');
|
146
|
-
await ctx.nextAuthDbAdapter.unlinkAccount({ provider, providerAccountId });
|
147
|
-
} else {
|
148
|
-
throw new Error('The method in LobeNextAuthDbAdapter `unlinkAccount` is not implemented');
|
149
|
-
}
|
137
|
+
const account = await ctx.nextAuthUserService.getAccount(providerAccountId, provider);
|
138
|
+
// The userId can either get from ctx.nextAuth?.id or ctx.userId
|
139
|
+
if (!account || account.userId !== ctx.userId) throw new Error('The account does not exist');
|
140
|
+
await ctx.nextAuthUserService.unlinkAccount({ provider, providerAccountId });
|
150
141
|
}),
|
151
142
|
|
152
143
|
// 服务端上传头像
|
@@ -1,18 +1,33 @@
|
|
1
|
+
import { and, eq } from 'drizzle-orm';
|
2
|
+
import { Adapter, AdapterAccount } from 'next-auth/adapters';
|
1
3
|
import { NextResponse } from 'next/server';
|
2
4
|
|
3
5
|
import { UserModel } from '@/database/models/user';
|
4
|
-
import {
|
6
|
+
import {
|
7
|
+
UserItem,
|
8
|
+
nextauthAccounts,
|
9
|
+
nextauthAuthenticators,
|
10
|
+
nextauthSessions,
|
11
|
+
nextauthVerificationTokens,
|
12
|
+
users,
|
13
|
+
} from '@/database/schemas';
|
5
14
|
import { LobeChatDatabase } from '@/database/type';
|
6
15
|
import { pino } from '@/libs/logger';
|
7
|
-
import {
|
16
|
+
import { merge } from '@/utils/merge';
|
17
|
+
|
18
|
+
import { AgentService } from '../agent';
|
19
|
+
import {
|
20
|
+
mapAdapterUserToLobeUser,
|
21
|
+
mapAuthenticatorQueryResutlToAdapterAuthenticator,
|
22
|
+
mapLobeUserToAdapterUser,
|
23
|
+
partialMapAdapterUserToLobeUser,
|
24
|
+
} from './utils';
|
8
25
|
|
9
26
|
export class NextAuthUserService {
|
10
|
-
adapter;
|
11
27
|
private db: LobeChatDatabase;
|
12
28
|
|
13
29
|
constructor(db: LobeChatDatabase) {
|
14
30
|
this.db = db;
|
15
|
-
this.adapter = LobeNextAuthDbAdapter(db);
|
16
31
|
}
|
17
32
|
|
18
33
|
safeUpdateUser = async (
|
@@ -21,8 +36,7 @@ export class NextAuthUserService {
|
|
21
36
|
) => {
|
22
37
|
pino.info(`updating user "${JSON.stringify({ provider, providerAccountId })}" due to webhook`);
|
23
38
|
// 1. Find User by account
|
24
|
-
|
25
|
-
const user = await this.adapter.getUserByAccount({
|
39
|
+
const user = await this.getUserByAccount({
|
26
40
|
provider,
|
27
41
|
providerAccountId,
|
28
42
|
});
|
@@ -44,4 +58,266 @@ export class NextAuthUserService {
|
|
44
58
|
}
|
45
59
|
return NextResponse.json({ message: 'user updated', success: true }, { status: 200 });
|
46
60
|
};
|
61
|
+
|
62
|
+
safeSignOutUser = async ({
|
63
|
+
providerAccountId,
|
64
|
+
provider,
|
65
|
+
}: {
|
66
|
+
provider: string;
|
67
|
+
providerAccountId: string;
|
68
|
+
}) => {
|
69
|
+
pino.info(`Signing out user "${JSON.stringify({ provider, providerAccountId })}"`);
|
70
|
+
const user = await this.getUserByAccount({
|
71
|
+
provider,
|
72
|
+
providerAccountId,
|
73
|
+
});
|
74
|
+
|
75
|
+
// 2. If found, Update user data from provider
|
76
|
+
if (user?.id) {
|
77
|
+
// Perform update
|
78
|
+
await this.db.delete(nextauthSessions).where(eq(nextauthSessions.userId, user.id));
|
79
|
+
} else {
|
80
|
+
pino.warn(
|
81
|
+
`[${provider}]: Webhooks handler user "${JSON.stringify({ provider, providerAccountId })}" to signout", but no user was found by the providerAccountId.`,
|
82
|
+
);
|
83
|
+
}
|
84
|
+
return NextResponse.json({ message: 'user signed out', success: true }, { status: 200 });
|
85
|
+
};
|
86
|
+
|
87
|
+
createAuthenticator: NonNullable<Adapter['createAuthenticator']> = async (authenticator) => {
|
88
|
+
return await this.db
|
89
|
+
.insert(nextauthAuthenticators)
|
90
|
+
.values(authenticator)
|
91
|
+
.returning()
|
92
|
+
.then((res: any) => res[0] ?? undefined);
|
93
|
+
};
|
94
|
+
|
95
|
+
createSession: NonNullable<Adapter['createSession']> = async (data) => {
|
96
|
+
return await this.db
|
97
|
+
.insert(nextauthSessions)
|
98
|
+
.values(data)
|
99
|
+
.returning()
|
100
|
+
.then((res: any) => res[0]);
|
101
|
+
};
|
102
|
+
|
103
|
+
createUser: NonNullable<Adapter['createUser']> = async (user) => {
|
104
|
+
const { id, name, email, emailVerified, image, providerAccountId } = user;
|
105
|
+
// return the user if it already exists
|
106
|
+
let existingUser =
|
107
|
+
email && typeof email === 'string' && email.trim()
|
108
|
+
? await UserModel.findByEmail(this.db, email)
|
109
|
+
: undefined;
|
110
|
+
// If the user is not found by email, try to find by providerAccountId
|
111
|
+
if (!existingUser && providerAccountId) {
|
112
|
+
existingUser = await UserModel.findById(this.db, providerAccountId);
|
113
|
+
}
|
114
|
+
if (existingUser) {
|
115
|
+
const adapterUser = mapLobeUserToAdapterUser(existingUser);
|
116
|
+
return adapterUser;
|
117
|
+
}
|
118
|
+
|
119
|
+
// create a new user if it does not exist
|
120
|
+
// Use id from provider if it exists, otherwise use id assigned by next-auth
|
121
|
+
// ref: https://github.com/lobehub/lobe-chat/pull/2935
|
122
|
+
const uid = providerAccountId ?? id;
|
123
|
+
await UserModel.createUser(
|
124
|
+
this.db,
|
125
|
+
mapAdapterUserToLobeUser({
|
126
|
+
email,
|
127
|
+
emailVerified,
|
128
|
+
// Use providerAccountId as userid to identify if the user exists in a SSO provider
|
129
|
+
id: uid,
|
130
|
+
image,
|
131
|
+
name,
|
132
|
+
}),
|
133
|
+
);
|
134
|
+
|
135
|
+
// 3. Create an inbox session for the user
|
136
|
+
const agentService = new AgentService(this.db, uid);
|
137
|
+
await agentService.createInbox();
|
138
|
+
|
139
|
+
return { ...user, id: uid };
|
140
|
+
};
|
141
|
+
|
142
|
+
createVerificationToken: NonNullable<Adapter['createVerificationToken']> = async (data) => {
|
143
|
+
return await this.db
|
144
|
+
.insert(nextauthVerificationTokens)
|
145
|
+
.values(data)
|
146
|
+
.returning()
|
147
|
+
.then((res: any) => res[0]);
|
148
|
+
};
|
149
|
+
|
150
|
+
deleteSession: NonNullable<Adapter['deleteSession']> = async (sessionToken) => {
|
151
|
+
await this.db.delete(nextauthSessions).where(eq(nextauthSessions.sessionToken, sessionToken));
|
152
|
+
};
|
153
|
+
|
154
|
+
deleteUser: NonNullable<Adapter['deleteUser']> = async (id) => {
|
155
|
+
const user = await UserModel.findById(this.db, id);
|
156
|
+
if (!user) throw new Error('NextAuth: Delete User not found');
|
157
|
+
await UserModel.deleteUser(this.db, id);
|
158
|
+
};
|
159
|
+
|
160
|
+
getAccount: NonNullable<Adapter['getAccount']> = async (providerAccountId, provider) => {
|
161
|
+
return (await this.db
|
162
|
+
.select()
|
163
|
+
.from(nextauthAccounts)
|
164
|
+
.where(
|
165
|
+
and(
|
166
|
+
eq(nextauthAccounts.provider, provider),
|
167
|
+
eq(nextauthAccounts.providerAccountId, providerAccountId),
|
168
|
+
),
|
169
|
+
)
|
170
|
+
.then((res: any) => res[0] ?? null)) as Promise<AdapterAccount | null>;
|
171
|
+
};
|
172
|
+
|
173
|
+
getAuthenticator: NonNullable<Adapter['getAuthenticator']> = async (credentialID) => {
|
174
|
+
const result = await this.db
|
175
|
+
.select()
|
176
|
+
.from(nextauthAuthenticators)
|
177
|
+
.where(eq(nextauthAuthenticators.credentialID, credentialID))
|
178
|
+
.then((res) => res[0] ?? null);
|
179
|
+
if (!result) throw new Error('NextAuthUserService: Failed to get authenticator');
|
180
|
+
return mapAuthenticatorQueryResutlToAdapterAuthenticator(result);
|
181
|
+
};
|
182
|
+
|
183
|
+
getSessionAndUser: NonNullable<Adapter['getSessionAndUser']> = async (sessionToken) => {
|
184
|
+
const result = await this.db
|
185
|
+
.select({
|
186
|
+
session: nextauthSessions,
|
187
|
+
user: users,
|
188
|
+
})
|
189
|
+
.from(nextauthSessions)
|
190
|
+
.where(eq(nextauthSessions.sessionToken, sessionToken))
|
191
|
+
.innerJoin(users, eq(users.id, nextauthSessions.userId))
|
192
|
+
.then((res: any) => (res.length > 0 ? res[0] : null));
|
193
|
+
|
194
|
+
if (!result) return null;
|
195
|
+
const adapterUser = mapLobeUserToAdapterUser(result.user);
|
196
|
+
if (!adapterUser) return null;
|
197
|
+
return {
|
198
|
+
session: result.session,
|
199
|
+
user: adapterUser,
|
200
|
+
};
|
201
|
+
};
|
202
|
+
|
203
|
+
getUser: NonNullable<Adapter['getUser']> = async (id) => {
|
204
|
+
const lobeUser = await UserModel.findById(this.db, id);
|
205
|
+
if (!lobeUser) return null;
|
206
|
+
return mapLobeUserToAdapterUser(lobeUser);
|
207
|
+
};
|
208
|
+
|
209
|
+
getUserByAccount: NonNullable<Adapter['getUserByAccount']> = async (account) => {
|
210
|
+
const result = await this.db
|
211
|
+
.select({
|
212
|
+
account: nextauthAccounts,
|
213
|
+
users,
|
214
|
+
})
|
215
|
+
.from(nextauthAccounts)
|
216
|
+
.innerJoin(users, eq(nextauthAccounts.userId, users.id))
|
217
|
+
.where(
|
218
|
+
and(
|
219
|
+
eq(nextauthAccounts.provider, account.provider),
|
220
|
+
eq(nextauthAccounts.providerAccountId, account.providerAccountId),
|
221
|
+
),
|
222
|
+
)
|
223
|
+
.then((res: any) => res[0]);
|
224
|
+
|
225
|
+
return result?.users ? mapLobeUserToAdapterUser(result.users) : null;
|
226
|
+
};
|
227
|
+
|
228
|
+
getUserByEmail: NonNullable<Adapter['getUserByEmail']> = async (email) => {
|
229
|
+
const lobeUser =
|
230
|
+
email && typeof email === 'string' && email.trim()
|
231
|
+
? await UserModel.findByEmail(this.db, email)
|
232
|
+
: undefined;
|
233
|
+
return lobeUser ? mapLobeUserToAdapterUser(lobeUser) : null;
|
234
|
+
};
|
235
|
+
|
236
|
+
linkAccount: NonNullable<Adapter['linkAccount']> = async (data) => {
|
237
|
+
const [account] = await this.db
|
238
|
+
.insert(nextauthAccounts)
|
239
|
+
.values(data as any)
|
240
|
+
.returning();
|
241
|
+
if (!account) throw new Error('NextAuthAccountModel: Failed to create account');
|
242
|
+
// TODO Update type annotation
|
243
|
+
return account as any;
|
244
|
+
};
|
245
|
+
|
246
|
+
listAuthenticatorsByUserId: NonNullable<Adapter['listAuthenticatorsByUserId']> = async (
|
247
|
+
userId,
|
248
|
+
) => {
|
249
|
+
const result = await this.db
|
250
|
+
.select()
|
251
|
+
.from(nextauthAuthenticators)
|
252
|
+
.where(eq(nextauthAuthenticators.userId, userId))
|
253
|
+
.then((res: any) => res);
|
254
|
+
if (result.length === 0)
|
255
|
+
throw new Error('NextAuthUserService: Failed to get authenticator list');
|
256
|
+
return result.map((r: any) => mapAuthenticatorQueryResutlToAdapterAuthenticator(r));
|
257
|
+
};
|
258
|
+
|
259
|
+
unlinkAccount: NonNullable<Adapter['unlinkAccount']> = async (account) => {
|
260
|
+
await this.db
|
261
|
+
.delete(nextauthAccounts)
|
262
|
+
.where(
|
263
|
+
and(
|
264
|
+
eq(nextauthAccounts.provider, account.provider),
|
265
|
+
eq(nextauthAccounts.providerAccountId, account.providerAccountId),
|
266
|
+
),
|
267
|
+
);
|
268
|
+
};
|
269
|
+
|
270
|
+
updateAuthenticatorCounter: NonNullable<Adapter['updateAuthenticatorCounter']> = async (
|
271
|
+
credentialID,
|
272
|
+
counter,
|
273
|
+
) => {
|
274
|
+
const result = await this.db
|
275
|
+
.update(nextauthAuthenticators)
|
276
|
+
.set({ counter })
|
277
|
+
.where(eq(nextauthAuthenticators.credentialID, credentialID))
|
278
|
+
.returning()
|
279
|
+
.then((res: any) => res[0]);
|
280
|
+
if (!result) throw new Error('NextAuthUserService: Failed to update authenticator counter');
|
281
|
+
return mapAuthenticatorQueryResutlToAdapterAuthenticator(result);
|
282
|
+
};
|
283
|
+
|
284
|
+
updateSession: NonNullable<Adapter['updateSession']> = async (data) => {
|
285
|
+
const res = await this.db
|
286
|
+
.update(nextauthSessions)
|
287
|
+
.set(data)
|
288
|
+
.where(eq(nextauthSessions.sessionToken, data.sessionToken))
|
289
|
+
.returning();
|
290
|
+
return res[0];
|
291
|
+
};
|
292
|
+
|
293
|
+
updateUser: NonNullable<Adapter['updateUser']> = async (user) => {
|
294
|
+
const lobeUser = await UserModel.findById(this.db, user?.id);
|
295
|
+
if (!lobeUser) throw new Error('NextAuth: User not found');
|
296
|
+
const userModel = new UserModel(this.db, user.id);
|
297
|
+
|
298
|
+
const updatedUser = await userModel.updateUser({
|
299
|
+
...partialMapAdapterUserToLobeUser(user),
|
300
|
+
});
|
301
|
+
if (!updatedUser) throw new Error('NextAuth: Failed to update user');
|
302
|
+
|
303
|
+
// merge new user data with old user data
|
304
|
+
const newAdapterUser = mapLobeUserToAdapterUser(lobeUser);
|
305
|
+
if (!newAdapterUser) {
|
306
|
+
throw new Error('NextAuth: Failed to map user data to adapter user');
|
307
|
+
}
|
308
|
+
return merge(newAdapterUser, user);
|
309
|
+
};
|
310
|
+
|
311
|
+
useVerificationToken: NonNullable<Adapter['useVerificationToken']> = async (identifier_token) => {
|
312
|
+
return await this.db
|
313
|
+
.delete(nextauthVerificationTokens)
|
314
|
+
.where(
|
315
|
+
and(
|
316
|
+
eq(nextauthVerificationTokens.identifier, identifier_token.identifier),
|
317
|
+
eq(nextauthVerificationTokens.token, identifier_token.token),
|
318
|
+
),
|
319
|
+
)
|
320
|
+
.returning()
|
321
|
+
.then((res: any) => (res.length > 0 ? res[0] : null));
|
322
|
+
};
|
47
323
|
}
|