Andrew commited on
Commit
274181d
·
1 Parent(s): 1a4f4cc

feat(auth): create shared function for user session updates

Browse files
src/routes/login/callback/userSession.ts ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getCoupledCookieHash, refreshSessionCookie } from "$lib/server/auth";
2
+ import { collections } from "$lib/server/database";
3
+ import { ObjectId } from "mongodb";
4
+ import { DEFAULT_SETTINGS } from "$lib/types/Settings";
5
+ import { error, type Cookies } from "@sveltejs/kit";
6
+ import crypto from "crypto";
7
+ import { sha256 } from "$lib/utils/sha256";
8
+ import { addWeeks } from "date-fns";
9
+ import { logger } from "$lib/server/logger";
10
+ import type { AuthProvider } from "$lib/types/User";
11
+ import { encryptToken } from "$lib/server/tokenEncryption";
12
+
13
+ export interface UserUpdateData {
14
+ authProvider: AuthProvider;
15
+ authId: string;
16
+ username?: string;
17
+ name: string;
18
+ email?: string;
19
+ avatarUrl?: string;
20
+ isAdmin?: boolean;
21
+ isEarlyAccess?: boolean;
22
+ }
23
+
24
+ export async function updateUserSession(params: {
25
+ userData: UserUpdateData;
26
+ locals: App.Locals;
27
+ cookies: Cookies;
28
+ userAgent?: string;
29
+ ip?: string;
30
+ hfAccessToken?: string;
31
+ }) {
32
+ const { userData, locals, cookies, userAgent, ip, hfAccessToken } = params;
33
+
34
+ logger.info(
35
+ {
36
+ authProvider: userData.authProvider,
37
+ authId: userData.authId,
38
+ login_username: userData.username,
39
+ login_name: userData.name,
40
+ login_email: userData.email,
41
+ },
42
+ "user login"
43
+ );
44
+
45
+ logger.debug(
46
+ {
47
+ isAdmin: userData.isAdmin,
48
+ isEarlyAccess: userData.isEarlyAccess,
49
+ authId: userData.authId,
50
+ },
51
+ `Updating user ${userData.authId}`
52
+ );
53
+
54
+ // check if user already exists (by authProvider + authId, or legacy hfUserId for HF)
55
+ const existingUser =
56
+ userData.authProvider === "huggingface"
57
+ ? await collections.users.findOne({
58
+ $or: [
59
+ { authProvider: "huggingface", authId: userData.authId },
60
+ { hfUserId: userData.authId }, // Legacy lookup
61
+ ],
62
+ })
63
+ : await collections.users.findOne({
64
+ authProvider: userData.authProvider,
65
+ authId: userData.authId,
66
+ });
67
+
68
+ let userId = existingUser?._id;
69
+
70
+ // update session cookie on login
71
+ const previousSessionId = locals.sessionId;
72
+ const secretSessionId = crypto.randomUUID();
73
+ const sessionId = await sha256(secretSessionId);
74
+
75
+ if (await collections.sessions.findOne({ sessionId })) {
76
+ error(500, "Session ID collision");
77
+ }
78
+
79
+ locals.sessionId = sessionId;
80
+
81
+ // Get cookie hash if coupling is enabled
82
+ const coupledCookieHash = await getCoupledCookieHash({ type: "svelte", value: cookies });
83
+
84
+ const userUpdateData = {
85
+ username: userData.username,
86
+ name: userData.name,
87
+ avatarUrl: userData.avatarUrl,
88
+ isAdmin: userData.isAdmin,
89
+ isEarlyAccess: userData.isEarlyAccess,
90
+ ...(userData.authProvider === "huggingface" ? { hfUserId: userData.authId } : {}),
91
+ };
92
+
93
+ if (existingUser) {
94
+ // Update existing user
95
+ await collections.users.updateOne(
96
+ { _id: existingUser._id },
97
+ {
98
+ $set: {
99
+ ...userUpdateData,
100
+ authProvider: userData.authProvider,
101
+ authId: userData.authId,
102
+ email: userData.email,
103
+ updatedAt: new Date(),
104
+ },
105
+ }
106
+ );
107
+
108
+ // remove previous session if it exists and add new one
109
+ await collections.sessions.deleteOne({ sessionId: previousSessionId });
110
+ await collections.sessions.insertOne({
111
+ _id: new ObjectId(),
112
+ sessionId: locals.sessionId,
113
+ userId: existingUser._id,
114
+ createdAt: new Date(),
115
+ updatedAt: new Date(),
116
+ userAgent,
117
+ ip,
118
+ expiresAt: addWeeks(new Date(), 2),
119
+ ...(coupledCookieHash ? { coupledCookieHash } : {}),
120
+ });
121
+ } else {
122
+ // user doesn't exist yet, create a new one
123
+ const { insertedId } = await collections.users.insertOne({
124
+ _id: new ObjectId(),
125
+ createdAt: new Date(),
126
+ updatedAt: new Date(),
127
+ username: userUpdateData.username,
128
+ name: userUpdateData.name,
129
+ avatarUrl: userUpdateData.avatarUrl,
130
+ isAdmin: userUpdateData.isAdmin,
131
+ isEarlyAccess: userUpdateData.isEarlyAccess,
132
+ authProvider: userData.authProvider,
133
+ authId: userData.authId,
134
+ email: userData.email,
135
+ ...(userData.authProvider === "huggingface" ? { hfUserId: userData.authId } : {}),
136
+ });
137
+
138
+ userId = insertedId;
139
+
140
+ await collections.sessions.insertOne({
141
+ _id: new ObjectId(),
142
+ sessionId: locals.sessionId,
143
+ userId,
144
+ createdAt: new Date(),
145
+ updatedAt: new Date(),
146
+ userAgent,
147
+ ip,
148
+ expiresAt: addWeeks(new Date(), 2),
149
+ ...(coupledCookieHash ? { coupledCookieHash } : {}),
150
+ });
151
+
152
+ // move pre-existing settings to new user
153
+ const { matchedCount } = await collections.settings.updateOne(
154
+ { sessionId: previousSessionId },
155
+ {
156
+ $set: { userId, updatedAt: new Date() },
157
+ $unset: { sessionId: "" },
158
+ }
159
+ );
160
+
161
+ if (!matchedCount) {
162
+ // if no settings found for user, create default settings
163
+ await collections.settings.insertOne({
164
+ userId,
165
+ updatedAt: new Date(),
166
+ createdAt: new Date(),
167
+ ...DEFAULT_SETTINGS,
168
+ });
169
+ }
170
+ }
171
+
172
+ // refresh session cookie
173
+ refreshSessionCookie(cookies, secretSessionId);
174
+
175
+ // migrate pre-existing conversations
176
+ await collections.conversations.updateMany(
177
+ { sessionId: previousSessionId },
178
+ {
179
+ $set: { userId },
180
+ $unset: { sessionId: "" },
181
+ }
182
+ );
183
+
184
+ // Manage stored Hugging Face tokens based on auth provider
185
+ if (userData.authProvider === "huggingface" && hfAccessToken) {
186
+ const encryptedToken = encryptToken(hfAccessToken);
187
+ await collections.userTokens.updateOne(
188
+ { userId, provider: "huggingface" },
189
+ {
190
+ $set: {
191
+ encryptedToken,
192
+ updatedAt: new Date(),
193
+ },
194
+ $setOnInsert: {
195
+ _id: new ObjectId(),
196
+ userId,
197
+ provider: "huggingface",
198
+ createdAt: new Date(),
199
+ },
200
+ },
201
+ { upsert: true }
202
+ );
203
+ } else {
204
+ await collections.userTokens.deleteOne({ userId, provider: "huggingface" });
205
+ }
206
+
207
+ return { userId };
208
+ }