b9f3a2ca0b
Replace the single `user.refreshToken` column with a proper Session
table so users can have multiple concurrent sessions (phone, laptop,
etc.), each with their own refresh token, expiry, label, and
remember-me flag.
- Add Session model (id, userId, tokenHash, label, userAgent,
ipAddress, rememberMe, lastUsedAt, expiresAt).
- Drop `User.refreshToken` and `User.refreshTokenExpiresAt`.
- authService: new createSession/validateSession/rotateSession/
revokeSession/listUserSessions helpers; remove refresh-token-on-user
functions.
- sessionCookies helper now issues a session_id cookie alongside
access_token and refresh_token; rotateSessionCookies keeps the same
session id on refresh.
- Login form adds a "Keep me signed in for 30 days" checkbox;
TTL is 7d by default, 30d with remember-me.
- User-Agent parsed into a friendly label ("Chrome on Windows") for
the upcoming sessions page.
- hooks.server.ts, refresh endpoint, logout, register, oauth callback,
and onboarding all switched to the new session API.
63 lines
2.1 KiB
SQL
63 lines
2.1 KiB
SQL
-- CreateTable
|
|
CREATE TABLE "Session" (
|
|
"id" TEXT NOT NULL PRIMARY KEY,
|
|
"userId" TEXT NOT NULL,
|
|
"tokenHash" TEXT NOT NULL,
|
|
"label" TEXT,
|
|
"userAgent" TEXT,
|
|
"ipAddress" TEXT,
|
|
"rememberMe" BOOLEAN NOT NULL DEFAULT false,
|
|
"lastUsedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"expiresAt" DATETIME NOT NULL,
|
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
);
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
|
|
|
-- CreateIndex
|
|
CREATE INDEX "Session_expiresAt_idx" ON "Session"("expiresAt");
|
|
|
|
-- RedefineTables: drop refreshToken + refreshTokenExpiresAt from User.
|
|
-- All existing user sessions will be invalidated; users must re-login once after upgrade.
|
|
PRAGMA foreign_keys=OFF;
|
|
|
|
CREATE TABLE "new_User" (
|
|
"id" TEXT NOT NULL PRIMARY KEY,
|
|
"email" TEXT NOT NULL,
|
|
"password" TEXT,
|
|
"displayName" TEXT NOT NULL,
|
|
"avatarUrl" TEXT,
|
|
"authProvider" TEXT NOT NULL DEFAULT 'local',
|
|
"role" TEXT NOT NULL DEFAULT 'user',
|
|
"onboardingComplete" BOOLEAN NOT NULL DEFAULT false,
|
|
"trackRecentApps" BOOLEAN NOT NULL DEFAULT true,
|
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
"updatedAt" DATETIME NOT NULL,
|
|
"themeMode" TEXT,
|
|
"primaryHue" INTEGER,
|
|
"primarySaturation" INTEGER,
|
|
"backgroundType" TEXT,
|
|
"locale" TEXT
|
|
);
|
|
|
|
INSERT INTO "new_User" (
|
|
"id", "email", "password", "displayName", "avatarUrl", "authProvider", "role",
|
|
"onboardingComplete", "trackRecentApps", "createdAt", "updatedAt",
|
|
"themeMode", "primaryHue", "primarySaturation", "backgroundType", "locale"
|
|
)
|
|
SELECT
|
|
"id", "email", "password", "displayName", "avatarUrl", "authProvider", "role",
|
|
"onboardingComplete", "trackRecentApps", "createdAt", "updatedAt",
|
|
"themeMode", "primaryHue", "primarySaturation", "backgroundType", "locale"
|
|
FROM "User";
|
|
|
|
DROP TABLE "User";
|
|
ALTER TABLE "new_User" RENAME TO "User";
|
|
|
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
|
CREATE INDEX "User_email_idx" ON "User"("email");
|
|
|
|
PRAGMA foreign_keys=ON;
|