Spaces:
Build error
Build error
Commit
•
e4a770a
1
Parent(s):
b1c120f
Session management improvements: Multi sessions, renew on login/logout (#603)
Browse files* wip: update sessionId on every login
* comment out object.freeze
* only refresh cookies on post
* Add support for multiple sessions per user
* fix tests
* 🛂 Hash sessionId in DB
* 🐛 do not forget about event.locals.sessionId
* Update src/lib/server/auth.ts
Co-authored-by: Eliott C. <[email protected]>
* Add `expiresAt` field
* remove index causing errors
* Fix bug where sessions were not properly being deleted on logout
* Moved session refresh outside of form content check
---------
Co-authored-by: coyotte508 <[email protected]>
- src/hooks.server.ts +35 -17
- src/lib/server/auth.ts +12 -2
- src/lib/server/database.ts +5 -0
- src/lib/types/MessageEvent.ts +2 -1
- src/lib/types/Session.ts +12 -0
- src/lib/types/User.ts +0 -3
- src/routes/login/callback/+page.server.ts +8 -2
- src/routes/login/callback/updateUser.spec.ts +7 -2
- src/routes/login/callback/updateUser.ts +62 -14
- src/routes/logout/+page.server.ts +4 -1
src/hooks.server.ts
CHANGED
@@ -7,18 +7,12 @@ import {
|
|
7 |
} from "$env/static/public";
|
8 |
import { collections } from "$lib/server/database";
|
9 |
import { base } from "$app/paths";
|
10 |
-
import { refreshSessionCookie, requiresUser } from "$lib/server/auth";
|
11 |
import { ERROR_MESSAGES } from "$lib/stores/errors";
|
|
|
|
|
12 |
|
13 |
export const handle: Handle = async ({ event, resolve }) => {
|
14 |
-
const token = event.cookies.get(COOKIE_NAME);
|
15 |
-
|
16 |
-
const user = token ? await collections.users.findOne({ sessionId: token }) : null;
|
17 |
-
|
18 |
-
if (user) {
|
19 |
-
event.locals.user = user;
|
20 |
-
}
|
21 |
-
|
22 |
function errorResponse(status: number, message: string) {
|
23 |
const sendJson =
|
24 |
event.request.headers.get("accept")?.includes("application/json") ||
|
@@ -31,17 +25,31 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
31 |
});
|
32 |
}
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
}
|
39 |
-
event.locals.sessionId = sessionId;
|
40 |
} else {
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
}
|
43 |
|
44 |
-
|
45 |
|
46 |
// CSRF protection
|
47 |
const requestContentType = event.request.headers.get("content-type")?.split(";")[0] ?? "";
|
@@ -73,13 +81,23 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|
73 |
}
|
74 |
}
|
75 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
if (
|
77 |
!event.url.pathname.startsWith(`${base}/login`) &&
|
78 |
!event.url.pathname.startsWith(`${base}/admin`) &&
|
79 |
!["GET", "OPTIONS", "HEAD"].includes(event.request.method)
|
80 |
) {
|
81 |
if (
|
82 |
-
!user &&
|
83 |
requiresUser &&
|
84 |
!((MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0) > 0)
|
85 |
) {
|
|
|
7 |
} from "$env/static/public";
|
8 |
import { collections } from "$lib/server/database";
|
9 |
import { base } from "$app/paths";
|
10 |
+
import { findUser, refreshSessionCookie, requiresUser } from "$lib/server/auth";
|
11 |
import { ERROR_MESSAGES } from "$lib/stores/errors";
|
12 |
+
import { sha256 } from "$lib/utils/sha256";
|
13 |
+
import { addWeeks } from "date-fns";
|
14 |
|
15 |
export const handle: Handle = async ({ event, resolve }) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
function errorResponse(status: number, message: string) {
|
17 |
const sendJson =
|
18 |
event.request.headers.get("accept")?.includes("application/json") ||
|
|
|
25 |
});
|
26 |
}
|
27 |
|
28 |
+
const token = event.cookies.get(COOKIE_NAME);
|
29 |
+
|
30 |
+
let secretSessionId: string;
|
31 |
+
let sessionId: string;
|
32 |
+
|
33 |
+
if (token) {
|
34 |
+
secretSessionId = token;
|
35 |
+
sessionId = await sha256(token);
|
36 |
+
|
37 |
+
const user = await findUser(sessionId);
|
38 |
+
|
39 |
+
if (user) {
|
40 |
+
event.locals.user = user;
|
41 |
}
|
|
|
42 |
} else {
|
43 |
+
// if the user doesn't have any cookie, we generate one for him
|
44 |
+
secretSessionId = crypto.randomUUID();
|
45 |
+
sessionId = await sha256(secretSessionId);
|
46 |
+
|
47 |
+
if (await collections.sessions.findOne({ sessionId })) {
|
48 |
+
return errorResponse(500, "Session ID collision");
|
49 |
+
}
|
50 |
}
|
51 |
|
52 |
+
event.locals.sessionId = sessionId;
|
53 |
|
54 |
// CSRF protection
|
55 |
const requestContentType = event.request.headers.get("content-type")?.split(";")[0] ?? "";
|
|
|
81 |
}
|
82 |
}
|
83 |
|
84 |
+
if (event.request.method === "POST") {
|
85 |
+
// if the request is a POST request we refresh the cookie
|
86 |
+
refreshSessionCookie(event.cookies, secretSessionId);
|
87 |
+
|
88 |
+
await collections.sessions.updateOne(
|
89 |
+
{ sessionId },
|
90 |
+
{ $set: { updatedAt: new Date(), expiresAt: addWeeks(new Date(), 2) } }
|
91 |
+
);
|
92 |
+
}
|
93 |
+
|
94 |
if (
|
95 |
!event.url.pathname.startsWith(`${base}/login`) &&
|
96 |
!event.url.pathname.startsWith(`${base}/admin`) &&
|
97 |
!["GET", "OPTIONS", "HEAD"].includes(event.request.method)
|
98 |
) {
|
99 |
if (
|
100 |
+
!event.locals.user &&
|
101 |
requiresUser &&
|
102 |
!((MESSAGES_BEFORE_LOGIN ? parseInt(MESSAGES_BEFORE_LOGIN) : 0) > 0)
|
103 |
) {
|
src/lib/server/auth.ts
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import { Issuer, BaseClient, type UserinfoResponse, TokenSet, custom } from "openid-client";
|
2 |
-
import { addHours,
|
3 |
import {
|
4 |
COOKIE_NAME,
|
5 |
OPENID_CLIENT_ID,
|
@@ -14,6 +14,7 @@ import { sha256 } from "$lib/utils/sha256";
|
|
14 |
import { z } from "zod";
|
15 |
import { dev } from "$app/environment";
|
16 |
import type { Cookies } from "@sveltejs/kit";
|
|
|
17 |
|
18 |
export interface OIDCSettings {
|
19 |
redirectURI: string;
|
@@ -50,10 +51,19 @@ export function refreshSessionCookie(cookies: Cookies, sessionId: string) {
|
|
50 |
sameSite: dev ? "lax" : "none",
|
51 |
secure: !dev,
|
52 |
httpOnly: true,
|
53 |
-
expires:
|
54 |
});
|
55 |
}
|
56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
export const authCondition = (locals: App.Locals) => {
|
58 |
return locals.user
|
59 |
? { userId: locals.user._id }
|
|
|
1 |
import { Issuer, BaseClient, type UserinfoResponse, TokenSet, custom } from "openid-client";
|
2 |
+
import { addHours, addWeeks } from "date-fns";
|
3 |
import {
|
4 |
COOKIE_NAME,
|
5 |
OPENID_CLIENT_ID,
|
|
|
14 |
import { z } from "zod";
|
15 |
import { dev } from "$app/environment";
|
16 |
import type { Cookies } from "@sveltejs/kit";
|
17 |
+
import { collections } from "./database";
|
18 |
|
19 |
export interface OIDCSettings {
|
20 |
redirectURI: string;
|
|
|
51 |
sameSite: dev ? "lax" : "none",
|
52 |
secure: !dev,
|
53 |
httpOnly: true,
|
54 |
+
expires: addWeeks(new Date(), 2),
|
55 |
});
|
56 |
}
|
57 |
|
58 |
+
export async function findUser(sessionId: string) {
|
59 |
+
const session = await collections.sessions.findOne({ sessionId: sessionId });
|
60 |
+
|
61 |
+
if (!session) {
|
62 |
+
return null;
|
63 |
+
}
|
64 |
+
|
65 |
+
return await collections.users.findOne({ _id: session.userId });
|
66 |
+
}
|
67 |
export const authCondition = (locals: App.Locals) => {
|
68 |
return locals.user
|
69 |
? { userId: locals.user._id }
|
src/lib/server/database.ts
CHANGED
@@ -7,6 +7,7 @@ import type { AbortedGeneration } from "$lib/types/AbortedGeneration";
|
|
7 |
import type { Settings } from "$lib/types/Settings";
|
8 |
import type { User } from "$lib/types/User";
|
9 |
import type { MessageEvent } from "$lib/types/MessageEvent";
|
|
|
10 |
|
11 |
if (!MONGODB_URL) {
|
12 |
throw new Error(
|
@@ -27,6 +28,7 @@ const sharedConversations = db.collection<SharedConversation>("sharedConversatio
|
|
27 |
const abortedGenerations = db.collection<AbortedGeneration>("abortedGenerations");
|
28 |
const settings = db.collection<Settings>("settings");
|
29 |
const users = db.collection<User>("users");
|
|
|
30 |
const webSearches = db.collection<WebSearch>("webSearches");
|
31 |
const messageEvents = db.collection<MessageEvent>("messageEvents");
|
32 |
const bucket = new GridFSBucket(db, { bucketName: "files" });
|
@@ -38,6 +40,7 @@ export const collections = {
|
|
38 |
abortedGenerations,
|
39 |
settings,
|
40 |
users,
|
|
|
41 |
webSearches,
|
42 |
messageEvents,
|
43 |
bucket,
|
@@ -65,4 +68,6 @@ client.on("open", () => {
|
|
65 |
users.createIndex({ hfUserId: 1 }, { unique: true }).catch(console.error);
|
66 |
users.createIndex({ sessionId: 1 }, { unique: true, sparse: true }).catch(console.error);
|
67 |
messageEvents.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(console.error);
|
|
|
|
|
68 |
});
|
|
|
7 |
import type { Settings } from "$lib/types/Settings";
|
8 |
import type { User } from "$lib/types/User";
|
9 |
import type { MessageEvent } from "$lib/types/MessageEvent";
|
10 |
+
import type { Session } from "$lib/types/Session";
|
11 |
|
12 |
if (!MONGODB_URL) {
|
13 |
throw new Error(
|
|
|
28 |
const abortedGenerations = db.collection<AbortedGeneration>("abortedGenerations");
|
29 |
const settings = db.collection<Settings>("settings");
|
30 |
const users = db.collection<User>("users");
|
31 |
+
const sessions = db.collection<Session>("sessions");
|
32 |
const webSearches = db.collection<WebSearch>("webSearches");
|
33 |
const messageEvents = db.collection<MessageEvent>("messageEvents");
|
34 |
const bucket = new GridFSBucket(db, { bucketName: "files" });
|
|
|
40 |
abortedGenerations,
|
41 |
settings,
|
42 |
users,
|
43 |
+
sessions,
|
44 |
webSearches,
|
45 |
messageEvents,
|
46 |
bucket,
|
|
|
68 |
users.createIndex({ hfUserId: 1 }, { unique: true }).catch(console.error);
|
69 |
users.createIndex({ sessionId: 1 }, { unique: true, sparse: true }).catch(console.error);
|
70 |
messageEvents.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 }).catch(console.error);
|
71 |
+
sessions.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 0 }).catch(console.error);
|
72 |
+
sessions.createIndex({ sessionId: 1 }, { unique: true }).catch(console.error);
|
73 |
});
|
src/lib/types/MessageEvent.ts
CHANGED
@@ -1,7 +1,8 @@
|
|
|
|
1 |
import type { Timestamps } from "./Timestamps";
|
2 |
import type { User } from "./User";
|
3 |
|
4 |
export interface MessageEvent extends Pick<Timestamps, "createdAt"> {
|
5 |
-
userId: User["_id"] |
|
6 |
ip?: string;
|
7 |
}
|
|
|
1 |
+
import type { Session } from "./Session";
|
2 |
import type { Timestamps } from "./Timestamps";
|
3 |
import type { User } from "./User";
|
4 |
|
5 |
export interface MessageEvent extends Pick<Timestamps, "createdAt"> {
|
6 |
+
userId: User["_id"] | Session["sessionId"];
|
7 |
ip?: string;
|
8 |
}
|
src/lib/types/Session.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { ObjectId } from "bson";
|
2 |
+
import type { Timestamps } from "./Timestamps";
|
3 |
+
import type { User } from "./User";
|
4 |
+
|
5 |
+
export interface Session extends Timestamps {
|
6 |
+
_id: ObjectId;
|
7 |
+
sessionId: string;
|
8 |
+
userId: User["_id"];
|
9 |
+
userAgent?: string;
|
10 |
+
ip?: string;
|
11 |
+
expiresAt: Date;
|
12 |
+
}
|
src/lib/types/User.ts
CHANGED
@@ -9,7 +9,4 @@ export interface User extends Timestamps {
|
|
9 |
email?: string;
|
10 |
avatarUrl: string;
|
11 |
hfUserId: string;
|
12 |
-
|
13 |
-
// Session identifier, stored in the cookie
|
14 |
-
sessionId: string;
|
15 |
}
|
|
|
9 |
email?: string;
|
10 |
avatarUrl: string;
|
11 |
hfUserId: string;
|
|
|
|
|
|
|
12 |
}
|
src/routes/login/callback/+page.server.ts
CHANGED
@@ -4,7 +4,7 @@ import { z } from "zod";
|
|
4 |
import { base } from "$app/paths";
|
5 |
import { updateUser } from "./updateUser";
|
6 |
|
7 |
-
export async function load({ url, locals, cookies }) {
|
8 |
const { error: errorName, error_description: errorDescription } = z
|
9 |
.object({
|
10 |
error: z.string().optional(),
|
@@ -33,7 +33,13 @@ export async function load({ url, locals, cookies }) {
|
|
33 |
|
34 |
const { userData } = await getOIDCUserData({ redirectURI: validatedToken.redirectUrl }, code);
|
35 |
|
36 |
-
await updateUser({
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
|
38 |
throw redirect(302, `${base}/`);
|
39 |
}
|
|
|
4 |
import { base } from "$app/paths";
|
5 |
import { updateUser } from "./updateUser";
|
6 |
|
7 |
+
export async function load({ url, locals, cookies, request, getClientAddress }) {
|
8 |
const { error: errorName, error_description: errorDescription } = z
|
9 |
.object({
|
10 |
error: z.string().optional(),
|
|
|
33 |
|
34 |
const { userData } = await getOIDCUserData({ redirectURI: validatedToken.redirectUrl }, code);
|
35 |
|
36 |
+
await updateUser({
|
37 |
+
userData,
|
38 |
+
locals,
|
39 |
+
cookies,
|
40 |
+
userAgent: request.headers.get("user-agent") ?? undefined,
|
41 |
+
ip: getClientAddress(),
|
42 |
+
});
|
43 |
|
44 |
throw redirect(302, `${base}/`);
|
45 |
}
|
src/routes/login/callback/updateUser.spec.ts
CHANGED
@@ -5,6 +5,7 @@ import { updateUser } from "./updateUser";
|
|
5 |
import { ObjectId } from "mongodb";
|
6 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
7 |
import { defaultModel } from "$lib/server/models";
|
|
|
8 |
|
9 |
const userData = {
|
10 |
preferred_username: "new-username",
|
@@ -12,6 +13,7 @@ const userData = {
|
|
12 |
picture: "https://example.com/avatar.png",
|
13 |
sub: "1234567890",
|
14 |
};
|
|
|
15 |
|
16 |
const locals = {
|
17 |
userId: "1234567890",
|
@@ -32,7 +34,6 @@ const insertRandomUser = async () => {
|
|
32 |
name: userData.name,
|
33 |
avatarUrl: userData.picture,
|
34 |
hfUserId: userData.sub,
|
35 |
-
sessionId: locals.sessionId,
|
36 |
});
|
37 |
|
38 |
return res.insertedId;
|
@@ -87,7 +88,7 @@ describe("login", () => {
|
|
87 |
it("should create default settings for new user", async () => {
|
88 |
await updateUser({ userData, locals, cookies: cookiesMock });
|
89 |
|
90 |
-
const user = await
|
91 |
|
92 |
assert.exists(user);
|
93 |
|
@@ -140,5 +141,9 @@ describe("login", () => {
|
|
140 |
|
141 |
afterEach(async () => {
|
142 |
await collections.users.deleteMany({ hfUserId: userData.sub });
|
|
|
|
|
|
|
|
|
143 |
vi.clearAllMocks();
|
144 |
});
|
|
|
5 |
import { ObjectId } from "mongodb";
|
6 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
7 |
import { defaultModel } from "$lib/server/models";
|
8 |
+
import { findUser } from "$lib/server/auth";
|
9 |
|
10 |
const userData = {
|
11 |
preferred_username: "new-username",
|
|
|
13 |
picture: "https://example.com/avatar.png",
|
14 |
sub: "1234567890",
|
15 |
};
|
16 |
+
Object.freeze(userData);
|
17 |
|
18 |
const locals = {
|
19 |
userId: "1234567890",
|
|
|
34 |
name: userData.name,
|
35 |
avatarUrl: userData.picture,
|
36 |
hfUserId: userData.sub,
|
|
|
37 |
});
|
38 |
|
39 |
return res.insertedId;
|
|
|
88 |
it("should create default settings for new user", async () => {
|
89 |
await updateUser({ userData, locals, cookies: cookiesMock });
|
90 |
|
91 |
+
const user = await findUser(locals.sessionId);
|
92 |
|
93 |
assert.exists(user);
|
94 |
|
|
|
141 |
|
142 |
afterEach(async () => {
|
143 |
await collections.users.deleteMany({ hfUserId: userData.sub });
|
144 |
+
await collections.sessions.deleteMany({});
|
145 |
+
|
146 |
+
locals.userId = "1234567890";
|
147 |
+
locals.sessionId = "1234567890";
|
148 |
vi.clearAllMocks();
|
149 |
});
|
src/routes/login/callback/updateUser.ts
CHANGED
@@ -1,17 +1,23 @@
|
|
1 |
-
import {
|
2 |
import { collections } from "$lib/server/database";
|
3 |
import { ObjectId } from "mongodb";
|
4 |
import { DEFAULT_SETTINGS } from "$lib/types/Settings";
|
5 |
import { z } from "zod";
|
6 |
import type { UserinfoResponse } from "openid-client";
|
7 |
-
import type
|
|
|
|
|
|
|
8 |
|
9 |
export async function updateUser(params: {
|
10 |
userData: UserinfoResponse;
|
11 |
locals: App.Locals;
|
12 |
cookies: Cookies;
|
|
|
|
|
13 |
}) {
|
14 |
-
const { userData, locals, cookies } = params;
|
|
|
15 |
const {
|
16 |
preferred_username: username,
|
17 |
name,
|
@@ -31,17 +37,43 @@ export async function updateUser(params: {
|
|
31 |
})
|
32 |
.parse(userData);
|
33 |
|
|
|
34 |
const existingUser = await collections.users.findOne({ hfUserId });
|
35 |
let userId = existingUser?._id;
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
if (existingUser) {
|
38 |
// update existing user if any
|
39 |
await collections.users.updateOne(
|
40 |
{ _id: existingUser._id },
|
41 |
{ $set: { username, name, avatarUrl } }
|
42 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
// refresh session cookie
|
44 |
-
refreshSessionCookie(cookies,
|
45 |
} else {
|
46 |
// user doesn't exist yet, create a new one
|
47 |
const { insertedId } = await collections.users.insertOne({
|
@@ -53,19 +85,32 @@ export async function updateUser(params: {
|
|
53 |
email,
|
54 |
avatarUrl,
|
55 |
hfUserId,
|
56 |
-
sessionId: locals.sessionId,
|
57 |
});
|
58 |
|
59 |
userId = insertedId;
|
60 |
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
65 |
});
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
if (!matchedCount) {
|
68 |
-
// create
|
69 |
await collections.settings.insertOne({
|
70 |
userId,
|
71 |
ethicsModalAcceptedAt: new Date(),
|
@@ -77,8 +122,11 @@ export async function updateUser(params: {
|
|
77 |
}
|
78 |
|
79 |
// migrate pre-existing conversations
|
80 |
-
await collections.conversations.updateMany(
|
81 |
-
|
82 |
-
|
83 |
-
|
|
|
|
|
|
|
84 |
}
|
|
|
1 |
+
import { 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 { z } from "zod";
|
6 |
import type { UserinfoResponse } from "openid-client";
|
7 |
+
import { error, type Cookies } from "@sveltejs/kit";
|
8 |
+
import crypto from "crypto";
|
9 |
+
import { sha256 } from "$lib/utils/sha256";
|
10 |
+
import { addWeeks } from "date-fns";
|
11 |
|
12 |
export async function updateUser(params: {
|
13 |
userData: UserinfoResponse;
|
14 |
locals: App.Locals;
|
15 |
cookies: Cookies;
|
16 |
+
userAgent?: string;
|
17 |
+
ip?: string;
|
18 |
}) {
|
19 |
+
const { userData, locals, cookies, userAgent, ip } = params;
|
20 |
+
|
21 |
const {
|
22 |
preferred_username: username,
|
23 |
name,
|
|
|
37 |
})
|
38 |
.parse(userData);
|
39 |
|
40 |
+
// check if user already exists
|
41 |
const existingUser = await collections.users.findOne({ hfUserId });
|
42 |
let userId = existingUser?._id;
|
43 |
|
44 |
+
// update session cookie on login
|
45 |
+
const previousSessionId = locals.sessionId;
|
46 |
+
const secretSessionId = crypto.randomUUID();
|
47 |
+
const sessionId = await sha256(secretSessionId);
|
48 |
+
|
49 |
+
if (await collections.sessions.findOne({ sessionId })) {
|
50 |
+
throw error(500, "Session ID collision");
|
51 |
+
}
|
52 |
+
|
53 |
+
locals.sessionId = sessionId;
|
54 |
+
|
55 |
if (existingUser) {
|
56 |
// update existing user if any
|
57 |
await collections.users.updateOne(
|
58 |
{ _id: existingUser._id },
|
59 |
{ $set: { username, name, avatarUrl } }
|
60 |
);
|
61 |
+
|
62 |
+
// remove previous session if it exists and add new one
|
63 |
+
await collections.sessions.deleteOne({ sessionId: previousSessionId });
|
64 |
+
await collections.sessions.insertOne({
|
65 |
+
_id: new ObjectId(),
|
66 |
+
sessionId: locals.sessionId,
|
67 |
+
userId: existingUser._id,
|
68 |
+
createdAt: new Date(),
|
69 |
+
updatedAt: new Date(),
|
70 |
+
userAgent,
|
71 |
+
ip,
|
72 |
+
expiresAt: addWeeks(new Date(), 2),
|
73 |
+
});
|
74 |
+
|
75 |
// refresh session cookie
|
76 |
+
refreshSessionCookie(cookies, secretSessionId);
|
77 |
} else {
|
78 |
// user doesn't exist yet, create a new one
|
79 |
const { insertedId } = await collections.users.insertOne({
|
|
|
85 |
email,
|
86 |
avatarUrl,
|
87 |
hfUserId,
|
|
|
88 |
});
|
89 |
|
90 |
userId = insertedId;
|
91 |
|
92 |
+
await collections.sessions.insertOne({
|
93 |
+
_id: new ObjectId(),
|
94 |
+
sessionId: locals.sessionId,
|
95 |
+
userId,
|
96 |
+
createdAt: new Date(),
|
97 |
+
updatedAt: new Date(),
|
98 |
+
userAgent,
|
99 |
+
ip,
|
100 |
+
expiresAt: addWeeks(new Date(), 2),
|
101 |
});
|
102 |
|
103 |
+
// move pre-existing settings to new user
|
104 |
+
const { matchedCount } = await collections.settings.updateOne(
|
105 |
+
{ sessionId: previousSessionId },
|
106 |
+
{
|
107 |
+
$set: { userId, updatedAt: new Date() },
|
108 |
+
$unset: { sessionId: "" },
|
109 |
+
}
|
110 |
+
);
|
111 |
+
|
112 |
if (!matchedCount) {
|
113 |
+
// if no settings found for user, create default settings
|
114 |
await collections.settings.insertOne({
|
115 |
userId,
|
116 |
ethicsModalAcceptedAt: new Date(),
|
|
|
122 |
}
|
123 |
|
124 |
// migrate pre-existing conversations
|
125 |
+
await collections.conversations.updateMany(
|
126 |
+
{ sessionId: previousSessionId },
|
127 |
+
{
|
128 |
+
$set: { userId },
|
129 |
+
$unset: { sessionId: "" },
|
130 |
+
}
|
131 |
+
);
|
132 |
}
|
src/routes/logout/+page.server.ts
CHANGED
@@ -1,10 +1,13 @@
|
|
1 |
import { dev } from "$app/environment";
|
2 |
import { base } from "$app/paths";
|
3 |
import { COOKIE_NAME } from "$env/static/private";
|
|
|
4 |
import { redirect } from "@sveltejs/kit";
|
5 |
|
6 |
export const actions = {
|
7 |
-
default: async function ({ cookies }) {
|
|
|
|
|
8 |
cookies.delete(COOKIE_NAME, {
|
9 |
path: "/",
|
10 |
// So that it works inside the space's iframe
|
|
|
1 |
import { dev } from "$app/environment";
|
2 |
import { base } from "$app/paths";
|
3 |
import { COOKIE_NAME } from "$env/static/private";
|
4 |
+
import { collections } from "$lib/server/database";
|
5 |
import { redirect } from "@sveltejs/kit";
|
6 |
|
7 |
export const actions = {
|
8 |
+
default: async function ({ cookies, locals }) {
|
9 |
+
await collections.sessions.deleteOne({ sessionId: locals.sessionId });
|
10 |
+
|
11 |
cookies.delete(COOKIE_NAME, {
|
12 |
path: "/",
|
13 |
// So that it works inside the space's iframe
|