@lobehub/lobehub 2.0.0-next.175 → 2.0.0-next.176
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/docs/development/database-schema.dbml +26 -0
- package/next.config.ts +27 -0
- package/package.json +3 -1
- package/packages/agent-runtime/package.json +4 -1
- package/packages/const/package.json +5 -0
- package/packages/database/migrations/0065_add_passkey.sql +22 -0
- package/packages/database/migrations/0066_add_document_fields.sql +8 -0
- package/packages/database/migrations/meta/0065_snapshot.json +9922 -0
- package/packages/database/migrations/meta/0066_snapshot.json +9962 -0
- package/packages/database/migrations/meta/_journal.json +14 -0
- package/packages/database/package.json +15 -0
- package/packages/database/src/core/migrations.json +54 -11
- package/packages/database/src/repositories/tableViewer/index.test.ts +1 -1
- package/packages/database/src/schemas/betterAuth.ts +40 -1
- package/packages/database/src/schemas/file.ts +9 -3
- package/packages/electron-client-ipc/package.json +3 -0
- package/packages/model-bank/package.json +3 -0
- package/packages/model-runtime/package.json +18 -1
- package/packages/types/package.json +11 -0
- package/packages/utils/package.json +24 -2
- package/packages/web-crawler/package.json +1 -1
- package/public/.well-known/apple-app-site-association +5 -0
- package/public/.well-known/assetlinks.json +18 -0
- package/src/auth.ts +69 -1
- package/src/libs/better-auth/email-templates/index.ts +1 -0
- package/src/libs/better-auth/email-templates/verification-otp.ts +106 -0
- package/src/libs/better-auth/utils/config.ts +20 -1
|
@@ -455,6 +455,20 @@
|
|
|
455
455
|
"when": 1766297832021,
|
|
456
456
|
"tag": "0064_add_agents_session_group_id",
|
|
457
457
|
"breakpoints": true
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"idx": 65,
|
|
461
|
+
"version": "7",
|
|
462
|
+
"when": 1766408202688,
|
|
463
|
+
"tag": "0065_add_passkey",
|
|
464
|
+
"breakpoints": true
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"idx": 66,
|
|
468
|
+
"version": "7",
|
|
469
|
+
"when": 1766474494249,
|
|
470
|
+
"tag": "0066_add_document_fields",
|
|
471
|
+
"breakpoints": true
|
|
458
472
|
}
|
|
459
473
|
],
|
|
460
474
|
"version": "6"
|
|
@@ -17,10 +17,25 @@
|
|
|
17
17
|
"@lobechat/const": "workspace:*",
|
|
18
18
|
"@lobechat/types": "workspace:*",
|
|
19
19
|
"@lobechat/utils": "workspace:*",
|
|
20
|
+
"@lobehub/charts": "^2.1.2",
|
|
21
|
+
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
|
22
|
+
"@neondatabase/serverless": "^1.0.2",
|
|
23
|
+
"@trpc/server": "^11.7.1",
|
|
24
|
+
"debug": "^4.4.3",
|
|
25
|
+
"drizzle-zod": "^0.5.1",
|
|
26
|
+
"lodash-es": "^4.17.21",
|
|
27
|
+
"model-bank": "workspace:*",
|
|
28
|
+
"next-auth": "5.0.0-beta.30",
|
|
29
|
+
"p-map": "^7.0.4",
|
|
20
30
|
"random-words": "^2.0.1",
|
|
21
31
|
"ts-md5": "^2.0.1",
|
|
32
|
+
"type-fest": "^5.2.0",
|
|
22
33
|
"ws": "^8.18.3"
|
|
23
34
|
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"dotenv": "^17.2.3",
|
|
37
|
+
"fake-indexeddb": "^6.2.5"
|
|
38
|
+
},
|
|
24
39
|
"peerDependencies": {
|
|
25
40
|
"@electric-sql/pglite": "^0.2.17",
|
|
26
41
|
"dayjs": ">=1.11.19",
|
|
@@ -223,7 +223,10 @@
|
|
|
223
223
|
"hash": "9646161fa041354714f823d726af27247bcd6e60fa3be5698c0d69f337a5700b"
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
|
-
"sql": [
|
|
226
|
+
"sql": [
|
|
227
|
+
"DROP TABLE \"user_budgets\";",
|
|
228
|
+
"\nDROP TABLE \"user_subscriptions\";"
|
|
229
|
+
],
|
|
227
230
|
"bps": true,
|
|
228
231
|
"folderMillis": 1729699958471,
|
|
229
232
|
"hash": "7dad43a2a25d1aec82124a4e53f8d82f8505c3073f23606c1dc5d2a4598eacf9"
|
|
@@ -295,7 +298,9 @@
|
|
|
295
298
|
"hash": "845a692ceabbfc3caf252a97d3e19a213bc0c433df2689900135f9cfded2cf49"
|
|
296
299
|
},
|
|
297
300
|
{
|
|
298
|
-
"sql": [
|
|
301
|
+
"sql": [
|
|
302
|
+
"ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"
|
|
303
|
+
],
|
|
299
304
|
"bps": true,
|
|
300
305
|
"folderMillis": 1737609172353,
|
|
301
306
|
"hash": "2cb36ae4fcdd7b7064767e04bfbb36ae34518ff4bb1b39006f2dd394d1893868"
|
|
@@ -510,7 +515,9 @@
|
|
|
510
515
|
"hash": "a7ccf007fd185ff922823148d1eae6fafe652fc98d2fd2793f84a84f29e93cd1"
|
|
511
516
|
},
|
|
512
517
|
{
|
|
513
|
-
"sql": [
|
|
518
|
+
"sql": [
|
|
519
|
+
"ALTER TABLE \"ai_providers\" ADD COLUMN \"config\" jsonb;"
|
|
520
|
+
],
|
|
514
521
|
"bps": true,
|
|
515
522
|
"folderMillis": 1749309388370,
|
|
516
523
|
"hash": "39cea379f08ee4cb944875c0b67f7791387b508c2d47958bb4cd501ed1ef33eb"
|
|
@@ -628,7 +635,9 @@
|
|
|
628
635
|
"hash": "1ba9b1f74ea13348da98d6fcdad7867ab4316ed565bf75d84d160c526cdac14b"
|
|
629
636
|
},
|
|
630
637
|
{
|
|
631
|
-
"sql": [
|
|
638
|
+
"sql": [
|
|
639
|
+
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"virtual\" boolean DEFAULT false;"
|
|
640
|
+
],
|
|
632
641
|
"bps": true,
|
|
633
642
|
"folderMillis": 1759116400580,
|
|
634
643
|
"hash": "433ddae88e785f2db734e49a4c115eee93e60afe389f7919d66e5ba9aa159a37"
|
|
@@ -678,13 +687,17 @@
|
|
|
678
687
|
"hash": "4bdc6505797d7a33b622498c138cfd47f637239f6905e1c484cd01d9d5f21d6b"
|
|
679
688
|
},
|
|
680
689
|
{
|
|
681
|
-
"sql": [
|
|
690
|
+
"sql": [
|
|
691
|
+
"ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"image\" jsonb;"
|
|
692
|
+
],
|
|
682
693
|
"bps": true,
|
|
683
694
|
"folderMillis": 1760108430562,
|
|
684
695
|
"hash": "ce09b301abb80f6563abc2f526bdd20b4f69bae430f09ba2179b9e3bfec43067"
|
|
685
696
|
},
|
|
686
697
|
{
|
|
687
|
-
"sql": [
|
|
698
|
+
"sql": [
|
|
699
|
+
"ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
|
|
700
|
+
],
|
|
688
701
|
"bps": true,
|
|
689
702
|
"folderMillis": 1761554153406,
|
|
690
703
|
"hash": "bf2f21293e90e11cf60a784cf3ec219eafa95f7545d7d2f9d1449c0b0949599a"
|
|
@@ -764,13 +777,17 @@
|
|
|
764
777
|
"hash": "923ccbdf46c32be9a981dabd348e6923b4a365444241e9b8cc174bf5b914cbc5"
|
|
765
778
|
},
|
|
766
779
|
{
|
|
767
|
-
"sql": [
|
|
780
|
+
"sql": [
|
|
781
|
+
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"market_identifier\" text;\n"
|
|
782
|
+
],
|
|
768
783
|
"bps": true,
|
|
769
784
|
"folderMillis": 1762870034882,
|
|
770
785
|
"hash": "4178aacb4b8892b7fd15d29209bbf9b1d1f9d7c406ba796f27542c0bcd919680"
|
|
771
786
|
},
|
|
772
787
|
{
|
|
773
|
-
"sql": [
|
|
788
|
+
"sql": [
|
|
789
|
+
"ALTER TABLE \"message_plugins\" ADD COLUMN IF NOT EXISTS \"intervention\" jsonb;\n"
|
|
790
|
+
],
|
|
774
791
|
"bps": true,
|
|
775
792
|
"folderMillis": 1762911968658,
|
|
776
793
|
"hash": "552a032cc0e595277232e70b5f9338658585bafe9481ae8346a5f322b673a68b"
|
|
@@ -799,7 +816,9 @@
|
|
|
799
816
|
"hash": "f823b521f4d25e5dc5ab238b372727d2d2d7f0aed27b5eabc8a9608ce4e50568"
|
|
800
817
|
},
|
|
801
818
|
{
|
|
802
|
-
"sql": [
|
|
819
|
+
"sql": [
|
|
820
|
+
"ALTER TABLE \"agents\" ADD COLUMN IF NOT EXISTS \"editor_data\" jsonb;"
|
|
821
|
+
],
|
|
803
822
|
"bps": true,
|
|
804
823
|
"folderMillis": 1764215503726,
|
|
805
824
|
"hash": "4188893a9083b3c7baebdbad0dd3f9d9400ede7584ca2394f5c64305dc9ec7b0"
|
|
@@ -840,7 +859,9 @@
|
|
|
840
859
|
"hash": "2c103eee82bdf329944fb622dd9c2b9f20df80eb54f23eb9254d2285de413099"
|
|
841
860
|
},
|
|
842
861
|
{
|
|
843
|
-
"sql": [
|
|
862
|
+
"sql": [
|
|
863
|
+
"ALTER TABLE \"user_settings\" ADD COLUMN IF NOT EXISTS \"market\" jsonb;"
|
|
864
|
+
],
|
|
844
865
|
"bps": true,
|
|
845
866
|
"folderMillis": 1764335703306,
|
|
846
867
|
"hash": "28c0d738c0b1fdf5fd871363be1a1477b4accbabdc140fe8dc6e9b339aae2c89"
|
|
@@ -1033,5 +1054,27 @@
|
|
|
1033
1054
|
"bps": true,
|
|
1034
1055
|
"folderMillis": 1766297832021,
|
|
1035
1056
|
"hash": "431a620396060130c46d6174d4bef3a517a0872aff8d19f3044bd9e7dec78ba5"
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
"sql": [
|
|
1060
|
+
"CREATE TABLE IF NOT EXISTS \"passkey\" (\n\t\"aaguid\" text,\n\t\"backedUp\" boolean,\n\t\"counter\" integer,\n\t\"createdAt\" timestamp DEFAULT now(),\n\t\"credentialID\" text NOT NULL,\n\t\"deviceType\" text,\n\t\"id\" text PRIMARY KEY NOT NULL,\n\t\"name\" text,\n\t\"publicKey\" text NOT NULL,\n\t\"transports\" text,\n\t\"userId\" text NOT NULL\n);\n",
|
|
1061
|
+
"\nDO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'passkey_userId_users_id_fk') THEN\n ALTER TABLE \"passkey\" ADD CONSTRAINT \"passkey_userId_users_id_fk\" FOREIGN KEY (\"userId\") REFERENCES \"public\".\"users\"(\"id\") ON DELETE cascade ON UPDATE no action;\n END IF;\nEND $$;\n",
|
|
1062
|
+
"\t\nCREATE UNIQUE INDEX IF NOT EXISTS \"passkey_credential_id_unique\" ON \"passkey\" USING btree (\"credentialID\");",
|
|
1063
|
+
"\nCREATE INDEX IF NOT EXISTS \"passkey_user_id_idx\" ON \"passkey\" USING btree (\"userId\");"
|
|
1064
|
+
],
|
|
1065
|
+
"bps": true,
|
|
1066
|
+
"folderMillis": 1766408202688,
|
|
1067
|
+
"hash": "1ae24d25f4b0ee7f38353b6d7fc6be4ae1171c105eb329d8aa97888abc85373b"
|
|
1068
|
+
},
|
|
1069
|
+
{
|
|
1070
|
+
"sql": [
|
|
1071
|
+
"ALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"description\" text;",
|
|
1072
|
+
"\nALTER TABLE \"documents\" ADD COLUMN IF NOT EXISTS \"knowledge_base_id\" text;",
|
|
1073
|
+
"\nDO $$ BEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'documents_knowledge_base_id_knowledge_bases_id_fk') THEN\n ALTER TABLE \"documents\" ADD CONSTRAINT \"documents_knowledge_base_id_knowledge_bases_id_fk\" FOREIGN KEY (\"knowledge_base_id\") REFERENCES \"public\".\"knowledge_bases\"(\"id\") ON DELETE set null ON UPDATE no action;\n END IF;\nEND $$;",
|
|
1074
|
+
"\nCREATE INDEX IF NOT EXISTS \"documents_knowledge_base_id_idx\" ON \"documents\" USING btree (\"knowledge_base_id\");\n"
|
|
1075
|
+
],
|
|
1076
|
+
"bps": true,
|
|
1077
|
+
"folderMillis": 1766474494249,
|
|
1078
|
+
"hash": "42d46c25f68aa3d6ab46b7c15734d984a9a8452e8058e98de79f4d675a38e28f"
|
|
1036
1079
|
}
|
|
1037
|
-
]
|
|
1080
|
+
]
|
|
@@ -23,7 +23,7 @@ describe('TableViewerRepo', () => {
|
|
|
23
23
|
it('should return all tables with counts', async () => {
|
|
24
24
|
const result = await repo.getAllTables();
|
|
25
25
|
|
|
26
|
-
expect(result.length).toEqual(
|
|
26
|
+
expect(result.length).toEqual(73);
|
|
27
27
|
expect(result[0]).toEqual({ name: 'accounts', count: 0, type: 'BASE TABLE' });
|
|
28
28
|
});
|
|
29
29
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { relations } from 'drizzle-orm';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
boolean,
|
|
4
|
+
index,
|
|
5
|
+
integer,
|
|
6
|
+
pgTable,
|
|
7
|
+
text,
|
|
8
|
+
timestamp,
|
|
9
|
+
uniqueIndex,
|
|
10
|
+
} from 'drizzle-orm/pg-core';
|
|
3
11
|
|
|
4
12
|
import { users } from './user';
|
|
5
13
|
|
|
@@ -92,8 +100,32 @@ export const twoFactor = pgTable(
|
|
|
92
100
|
],
|
|
93
101
|
);
|
|
94
102
|
|
|
103
|
+
export const passkey = pgTable(
|
|
104
|
+
'passkey',
|
|
105
|
+
{
|
|
106
|
+
aaguid: text('aaguid'),
|
|
107
|
+
backedUp: boolean('backedUp'),
|
|
108
|
+
counter: integer('counter'),
|
|
109
|
+
createdAt: timestamp('createdAt').defaultNow(),
|
|
110
|
+
credentialID: text('credentialID').notNull(),
|
|
111
|
+
deviceType: text('deviceType'),
|
|
112
|
+
id: text('id').primaryKey(),
|
|
113
|
+
name: text('name'),
|
|
114
|
+
publicKey: text('publicKey').notNull(),
|
|
115
|
+
transports: text('transports'),
|
|
116
|
+
userId: text('userId')
|
|
117
|
+
.notNull()
|
|
118
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
119
|
+
},
|
|
120
|
+
(table) => [
|
|
121
|
+
uniqueIndex('passkey_credential_id_unique').on(table.credentialID),
|
|
122
|
+
index('passkey_user_id_idx').on(table.userId),
|
|
123
|
+
],
|
|
124
|
+
);
|
|
125
|
+
|
|
95
126
|
export const usersRelations = relations(users, ({ many }) => ({
|
|
96
127
|
accounts: many(account),
|
|
128
|
+
passkeys: many(passkey),
|
|
97
129
|
sessions: many(session),
|
|
98
130
|
twoFactors: many(twoFactor),
|
|
99
131
|
}));
|
|
@@ -118,3 +150,10 @@ export const twoFactorRelations = relations(twoFactor, ({ one }) => ({
|
|
|
118
150
|
references: [users.id],
|
|
119
151
|
}),
|
|
120
152
|
}));
|
|
153
|
+
|
|
154
|
+
export const passkeysRelations = relations(passkey, ({ one }) => ({
|
|
155
|
+
users: one(users, {
|
|
156
|
+
fields: [passkey.userId],
|
|
157
|
+
references: [users.id],
|
|
158
|
+
}),
|
|
159
|
+
}));
|
|
@@ -18,7 +18,7 @@ import { createInsertSchema } from 'drizzle-zod';
|
|
|
18
18
|
import { LobeDocumentPage } from '@/types/document';
|
|
19
19
|
import { FileSource } from '@/types/files';
|
|
20
20
|
|
|
21
|
-
import { idGenerator } from '../utils/idGenerator';
|
|
21
|
+
import { idGenerator, randomSlug } from '../utils/idGenerator';
|
|
22
22
|
import { accessedAt, createdAt, timestamps } from './_helpers';
|
|
23
23
|
import { asyncTasks } from './asyncTask';
|
|
24
24
|
import { users } from './user';
|
|
@@ -55,6 +55,7 @@ export const documents = pgTable(
|
|
|
55
55
|
|
|
56
56
|
// Basic information
|
|
57
57
|
title: text('title'),
|
|
58
|
+
description: text('description'),
|
|
58
59
|
content: text('content'),
|
|
59
60
|
|
|
60
61
|
// Special type: custom/folder
|
|
@@ -72,13 +73,17 @@ export const documents = pgTable(
|
|
|
72
73
|
pages: jsonb('pages').$type<LobeDocumentPage[]>(),
|
|
73
74
|
|
|
74
75
|
// Source type
|
|
75
|
-
sourceType: text('source_type', { enum: ['file', 'web', 'api'] }).notNull(),
|
|
76
|
+
sourceType: text('source_type', { enum: ['file', 'web', 'api', 'topic'] }).notNull(),
|
|
76
77
|
source: text('source').notNull(), // File path or web URL
|
|
77
78
|
|
|
78
79
|
// Associated file (optional)
|
|
79
80
|
// forward reference needs AnyPgColumn to avoid circular type inference
|
|
80
81
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
81
82
|
fileId: text('file_id').references((): AnyPgColumn => files.id, { onDelete: 'set null' }),
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
84
|
+
knowledgeBaseId: text('knowledge_base_id').references(() => knowledgeBases.id, {
|
|
85
|
+
onDelete: 'set null',
|
|
86
|
+
}),
|
|
82
87
|
|
|
83
88
|
// Parent document (for folder hierarchy structure)
|
|
84
89
|
parentId: varchar('parent_id', { length: 255 }).references((): AnyPgColumn => documents.id, {
|
|
@@ -93,7 +98,7 @@ export const documents = pgTable(
|
|
|
93
98
|
|
|
94
99
|
editorData: jsonb('editor_data').$type<Record<string, any>>(),
|
|
95
100
|
|
|
96
|
-
slug: varchar('slug', { length: 255 }),
|
|
101
|
+
slug: varchar('slug', { length: 255 }).$defaultFn(() => randomSlug(3)),
|
|
97
102
|
|
|
98
103
|
// Timestamps
|
|
99
104
|
...timestamps,
|
|
@@ -105,6 +110,7 @@ export const documents = pgTable(
|
|
|
105
110
|
index('documents_user_id_idx').on(table.userId),
|
|
106
111
|
index('documents_file_id_idx').on(table.fileId),
|
|
107
112
|
index('documents_parent_id_idx').on(table.parentId),
|
|
113
|
+
index('documents_knowledge_base_id_idx').on(table.knowledgeBaseId),
|
|
108
114
|
uniqueIndex('documents_client_id_user_id_unique').on(table.clientId, table.userId),
|
|
109
115
|
uniqueIndex('documents_slug_user_id_unique')
|
|
110
116
|
.on(table.slug, table.userId)
|
|
@@ -12,15 +12,32 @@
|
|
|
12
12
|
"test:update": "vitest -u"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
+
"@anthropic-ai/sdk": "^0.67.1",
|
|
15
16
|
"@aws-sdk/client-bedrock-runtime": "^3.941.0",
|
|
17
|
+
"@azure-rest/ai-inference": "1.0.0-beta.5",
|
|
18
|
+
"@azure/core-auth": "^1.10.1",
|
|
19
|
+
"@fal-ai/client": "^1.7.2",
|
|
20
|
+
"@google/genai": "^1.29.0",
|
|
16
21
|
"@huggingface/inference": "^4.13.4",
|
|
17
22
|
"@lobechat/const": "workspace:*",
|
|
18
23
|
"@lobechat/types": "workspace:*",
|
|
19
24
|
"@lobechat/utils": "workspace:*",
|
|
20
25
|
"async-retry": "^1.3.3",
|
|
26
|
+
"dayjs": "^1.11.19",
|
|
21
27
|
"debug": "^4.4.3",
|
|
28
|
+
"immer": "^10.2.0",
|
|
29
|
+
"langfuse": "^3.38.6",
|
|
30
|
+
"langfuse-core": "^3.38.6",
|
|
31
|
+
"lodash-es": "^4.17.21",
|
|
22
32
|
"model-bank": "workspace:*",
|
|
33
|
+
"nanoid": "^5.1.6",
|
|
34
|
+
"ollama": "^0.6.2",
|
|
23
35
|
"openai": "^4.104.0",
|
|
24
|
-
"replicate": "^1.4.0"
|
|
36
|
+
"replicate": "^1.4.0",
|
|
37
|
+
"type-fest": "^5.2.0",
|
|
38
|
+
"url-join": "^5.0.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"zod": "^3.25.76"
|
|
25
42
|
}
|
|
26
43
|
}
|
|
@@ -4,9 +4,20 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"dependencies": {
|
|
7
|
+
"@lobechat/model-runtime": "workspace:*",
|
|
8
|
+
"@lobechat/python-interpreter": "workspace:*",
|
|
7
9
|
"@lobechat/web-crawler": "workspace:*",
|
|
8
10
|
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
|
11
|
+
"@lobehub/market-sdk": "beta",
|
|
12
|
+
"@lobehub/market-types": "^1.11.4",
|
|
13
|
+
"@lobehub/ui": "^2.13.8",
|
|
14
|
+
"model-bank": "workspace:*",
|
|
15
|
+
"react": "19.2.0",
|
|
9
16
|
"type-fest": "^4.41.0",
|
|
17
|
+
"zod": "^3.25.76",
|
|
18
|
+
"zustand": "5.0.4"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
10
21
|
"zod": "^3.25.76"
|
|
11
22
|
}
|
|
12
23
|
}
|
|
@@ -16,10 +16,32 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@lobechat/const": "workspace:*",
|
|
18
18
|
"@lobechat/types": "workspace:*",
|
|
19
|
+
"@lobehub/chat-plugin-sdk": "^1.32.4",
|
|
20
|
+
"@vercel/functions": "^3.3.0",
|
|
21
|
+
"brotli-wasm": "^3.0.1",
|
|
22
|
+
"chroma-js": "^3.1.2",
|
|
23
|
+
"countries-and-timezones": "^3.8.0",
|
|
19
24
|
"dayjs": "^1.11.19",
|
|
20
|
-
"
|
|
25
|
+
"debug": "^4.4.3",
|
|
26
|
+
"dompurify": "^3.3.0",
|
|
27
|
+
"fast-deep-equal": "^3.1.3",
|
|
28
|
+
"lodash-es": "^4.17.21",
|
|
29
|
+
"mime": "^4.1.0",
|
|
30
|
+
"model-bank": "workspace:*",
|
|
31
|
+
"nanoid": "^5.1.6",
|
|
32
|
+
"next": "^16.0.1",
|
|
33
|
+
"numeral": "^2.0.6",
|
|
34
|
+
"pure-rand": "^7.0.1",
|
|
35
|
+
"remark": "^15.0.1",
|
|
36
|
+
"remark-gfm": "^4.0.1",
|
|
37
|
+
"remark-html": "^16.0.1",
|
|
38
|
+
"ssrf-safe-fetch": "workspace:*",
|
|
39
|
+
"tokenx": "^1.2.1",
|
|
40
|
+
"ua-parser-js": "^1.0.41",
|
|
41
|
+
"uuid": "^11.1.0",
|
|
42
|
+
"yaml": "^2.8.1"
|
|
21
43
|
},
|
|
22
44
|
"devDependencies": {
|
|
23
|
-
"vitest-canvas-mock": "^
|
|
45
|
+
"vitest-canvas-mock": "^1.1.3"
|
|
24
46
|
}
|
|
25
47
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"relation": [
|
|
4
|
+
"delegate_permission/common.handle_all_urls",
|
|
5
|
+
"delegate_permission/common.get_login_creds"
|
|
6
|
+
],
|
|
7
|
+
"target": {
|
|
8
|
+
"namespace": "android_app",
|
|
9
|
+
"package_name": "com.lobehub.app",
|
|
10
|
+
"sha256_cert_fingerprints": [
|
|
11
|
+
"D7:54:DB:A3:78:D5:8B:8F:20:01:ED:7B:9B:18:D3:B0:5B:D1:22:AA:97:2B:59:E1:A6:8E:31:24:21:44:0D:2B",
|
|
12
|
+
"FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C",
|
|
13
|
+
"1B:21:38:5D:72:40:65:F5:16:20:1D:C9:D2:6B:04:63:C3:33:F1:97:AB:6A:06:66:0E:3E:F0:7E:60:82:7E:E7",
|
|
14
|
+
"1B:BE:D4:A0:AE:43:56:E5:58:01:74:C4:B9:A0:0B:0E:5A:B9:5E:0F:A9:C0:65:18:68:CF:1F:AA:3E:8F:4F:DB"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
]
|
package/src/auth.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
/* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
|
|
2
|
+
import { expo } from '@better-auth/expo';
|
|
3
|
+
import { passkey } from '@better-auth/passkey';
|
|
2
4
|
import { createNanoId, idGenerator, serverDB } from '@lobechat/database';
|
|
5
|
+
import * as schema from '@lobechat/database/schemas';
|
|
3
6
|
import { emailHarmony } from 'better-auth-harmony';
|
|
4
7
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
|
|
5
8
|
import { betterAuth } from 'better-auth/minimal';
|
|
6
|
-
import { admin, genericOAuth, magicLink } from 'better-auth/plugins';
|
|
9
|
+
import { admin, emailOTP, genericOAuth, magicLink } from 'better-auth/plugins';
|
|
7
10
|
|
|
8
11
|
import { authEnv } from '@/envs/auth';
|
|
9
12
|
import {
|
|
10
13
|
getMagicLinkEmailTemplate,
|
|
11
14
|
getResetPasswordEmailTemplate,
|
|
12
15
|
getVerificationEmailTemplate,
|
|
16
|
+
getVerificationOTPEmailTemplate,
|
|
13
17
|
} from '@/libs/better-auth/email-templates';
|
|
14
18
|
import { initBetterAuthSSOProviders } from '@/libs/better-auth/sso';
|
|
15
19
|
import { createSecondaryStorage, getTrustedOrigins } from '@/libs/better-auth/utils/config';
|
|
@@ -20,7 +24,34 @@ import { UserService } from '@/server/services/user';
|
|
|
20
24
|
// Email verification link expiration time (in seconds)
|
|
21
25
|
// Default is 1 hour (3600 seconds) as per Better Auth documentation
|
|
22
26
|
const VERIFICATION_LINK_EXPIRES_IN = 3600;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Safely extract hostname from AUTH_URL for passkey rpID.
|
|
30
|
+
* Returns undefined if AUTH_URL is not set (e.g., in e2e tests).
|
|
31
|
+
*/
|
|
32
|
+
const getPasskeyRpID = (): string | undefined => {
|
|
33
|
+
if (!authEnv.NEXT_PUBLIC_AUTH_URL) return undefined;
|
|
34
|
+
try {
|
|
35
|
+
return new URL(authEnv.NEXT_PUBLIC_AUTH_URL).hostname;
|
|
36
|
+
} catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get passkey origins array.
|
|
43
|
+
* Returns undefined if AUTH_URL is not set (e.g., in e2e tests).
|
|
44
|
+
*/
|
|
45
|
+
const getPasskeyOrigins = (): string[] | undefined => {
|
|
46
|
+
if (!authEnv.NEXT_PUBLIC_AUTH_URL) return undefined;
|
|
47
|
+
return [
|
|
48
|
+
// Web origin
|
|
49
|
+
authEnv.NEXT_PUBLIC_AUTH_URL,
|
|
50
|
+
];
|
|
51
|
+
};
|
|
23
52
|
const MAGIC_LINK_EXPIRES_IN = 900;
|
|
53
|
+
// OTP expiration time (in seconds) - 5 minutes for mobile OTP verification
|
|
54
|
+
const OTP_EXPIRES_IN = 300;
|
|
24
55
|
const enableMagicLink = authEnv.NEXT_PUBLIC_ENABLE_MAGIC_LINK;
|
|
25
56
|
const enabledSSOProviders = parseSSOProviders(authEnv.AUTH_SSO_PROVIDERS);
|
|
26
57
|
|
|
@@ -85,6 +116,8 @@ export const auth = betterAuth({
|
|
|
85
116
|
},
|
|
86
117
|
database: drizzleAdapter(serverDB, {
|
|
87
118
|
provider: 'pg',
|
|
119
|
+
// experimental joins feature needs schema to pass full relation
|
|
120
|
+
schema,
|
|
88
121
|
}),
|
|
89
122
|
secondaryStorage: createSecondaryStorage(),
|
|
90
123
|
/**
|
|
@@ -149,8 +182,43 @@ export const auth = betterAuth({
|
|
|
149
182
|
},
|
|
150
183
|
},
|
|
151
184
|
plugins: [
|
|
185
|
+
expo(),
|
|
152
186
|
emailHarmony({ allowNormalizedSignin: false }),
|
|
153
187
|
admin(),
|
|
188
|
+
// Email OTP plugin for mobile verification
|
|
189
|
+
emailOTP({
|
|
190
|
+
expiresIn: OTP_EXPIRES_IN,
|
|
191
|
+
otpLength: 6,
|
|
192
|
+
allowedAttempts: 3,
|
|
193
|
+
// Don't automatically send OTP on sign up - let mobile client manually trigger it
|
|
194
|
+
sendVerificationOnSignUp: false,
|
|
195
|
+
async sendVerificationOTP({ email, otp }) {
|
|
196
|
+
const emailService = new EmailService();
|
|
197
|
+
|
|
198
|
+
// For all OTP types, use the same template
|
|
199
|
+
// userName is optional and will be null since we don't have user context here
|
|
200
|
+
const template = getVerificationOTPEmailTemplate({
|
|
201
|
+
expiresInSeconds: OTP_EXPIRES_IN,
|
|
202
|
+
otp,
|
|
203
|
+
userName: null,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await emailService.sendMail({
|
|
207
|
+
to: email,
|
|
208
|
+
...template,
|
|
209
|
+
});
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
212
|
+
passkey({
|
|
213
|
+
rpName: 'LobeHub',
|
|
214
|
+
// Extract rpID from auth URL (e.g., 'lobehub.com' from 'https://lobehub.com')
|
|
215
|
+
// Returns undefined if AUTH_URL is not set (e.g., in e2e tests)
|
|
216
|
+
rpID: getPasskeyRpID(),
|
|
217
|
+
// Support multiple origins: web + Android APK key hashes
|
|
218
|
+
// Android origin format: android:apk-key-hash:<base64url-sha256-fingerprint>
|
|
219
|
+
// Returns undefined if AUTH_URL is not set (e.g., in e2e tests)
|
|
220
|
+
origin: getPasskeyOrigins(),
|
|
221
|
+
}),
|
|
154
222
|
...(genericOAuthProviders.length > 0
|
|
155
223
|
? [
|
|
156
224
|
genericOAuth({
|