@rebasepro/server-postgres 0.10.1-canary.18115ba → 0.10.1-canary.31c773c
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/dist/auth/services.d.ts +4 -43
- package/dist/index.es.js +71 -212
- package/dist/index.es.js.map +1 -1
- package/dist/schema/auth-schema.d.ts +0 -170
- package/package.json +6 -6
- package/src/auth/ensure-tables.ts +56 -70
- package/src/auth/services.ts +48 -186
- package/src/schema/auth-schema.ts +3 -41
package/dist/auth/services.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
2
|
import type { RebasePgTable } from "../types";
|
|
3
|
-
import { UserRepository, TokenRepository, MfaRepository, AuthRepository, UserData, CreateUserData, RoleData, CreateRoleData, RefreshTokenInfo,
|
|
3
|
+
import { UserRepository, TokenRepository, MfaRepository, AuthRepository, UserData, CreateUserData, RoleData, CreateRoleData, RefreshTokenInfo, PasswordResetTokenInfo, MagicLinkTokenInfo, UserIdentityData, ListUsersOptions, PaginatedUsersResult, MfaFactor, MfaChallengeInfo, RoleData as Role } from "@rebasepro/server";
|
|
4
4
|
export type { Role };
|
|
5
5
|
export interface AuthSchemaTables {
|
|
6
6
|
users: RebasePgTable;
|
|
@@ -90,38 +90,9 @@ export declare class UserService implements UserRepository {
|
|
|
90
90
|
export declare class RefreshTokenService {
|
|
91
91
|
private db;
|
|
92
92
|
private refreshTokensTable;
|
|
93
|
-
private usersTable;
|
|
94
93
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
95
|
-
|
|
96
|
-
* Whether the table actually carries a column, so a host application that
|
|
97
|
-
* supplied its own `refresh_tokens` table — one that predates session
|
|
98
|
-
* grouping — degrades instead of throwing on every sign-in.
|
|
99
|
-
*/
|
|
100
|
-
private has;
|
|
101
|
-
private col;
|
|
102
|
-
/** The columns to read back, narrowed to the ones this table has. */
|
|
103
|
-
private selection;
|
|
104
|
-
createToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string, session?: RefreshTokenSession): Promise<void>;
|
|
94
|
+
createToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
105
95
|
findByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
106
|
-
/**
|
|
107
|
-
* Record that a token was rotated away, keeping the row.
|
|
108
|
-
*
|
|
109
|
-
* The row is what lets `/auth/refresh` distinguish "you already used this,
|
|
110
|
-
* here is a fresh one" from "no idea what this is". Deleting it — which is
|
|
111
|
-
* what this used to do — collapsed both into a 401 and signed the user out
|
|
112
|
-
* for the crime of losing a response.
|
|
113
|
-
*/
|
|
114
|
-
markRotated(tokenHash: string): Promise<void>;
|
|
115
|
-
/** Final kill of one sign-in: logout, or revoking a device remotely. */
|
|
116
|
-
revokeSession(sessionId: string): Promise<void>;
|
|
117
|
-
/**
|
|
118
|
-
* Housekeeping: rotation would otherwise leave a row per refresh forever.
|
|
119
|
-
* Superseded rows are only needed for as long as a straggler might still
|
|
120
|
-
* present them, and expired ones are dead weight everywhere.
|
|
121
|
-
*/
|
|
122
|
-
prune(uid: string, sessionId: string, supersededBefore: Date): Promise<void>;
|
|
123
|
-
getTokensValidAfter(uid: string): Promise<Date | null>;
|
|
124
|
-
setTokensValidAfter(uid: string, at: Date): Promise<void>;
|
|
125
96
|
deleteByHash(tokenHash: string): Promise<void>;
|
|
126
97
|
deleteAllForUser(uid: string): Promise<void>;
|
|
127
98
|
listForUser(uid: string): Promise<RefreshTokenInfo[]>;
|
|
@@ -182,12 +153,7 @@ export declare class PostgresTokenRepository implements TokenRepository {
|
|
|
182
153
|
private passwordResetTokenService;
|
|
183
154
|
private magicLinkTokenService;
|
|
184
155
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
185
|
-
createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string
|
|
186
|
-
markRefreshTokenRotated(tokenHash: string): Promise<void>;
|
|
187
|
-
revokeRefreshTokenSession(sessionId: string): Promise<void>;
|
|
188
|
-
pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void>;
|
|
189
|
-
getTokensValidAfter(uid: string): Promise<Date | null>;
|
|
190
|
-
setTokensValidAfter(uid: string, at: Date): Promise<void>;
|
|
156
|
+
createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
191
157
|
findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
192
158
|
deleteRefreshToken(tokenHash: string): Promise<void>;
|
|
193
159
|
deleteAllRefreshTokensForUser(uid: string): Promise<void>;
|
|
@@ -239,12 +205,7 @@ export declare class PostgresAuthRepository implements AuthRepository {
|
|
|
239
205
|
createRole(_data: CreateRoleData): Promise<RoleData>;
|
|
240
206
|
updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
|
|
241
207
|
deleteRole(_id: string): Promise<void>;
|
|
242
|
-
createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string
|
|
243
|
-
markRefreshTokenRotated(tokenHash: string): Promise<void>;
|
|
244
|
-
revokeRefreshTokenSession(sessionId: string): Promise<void>;
|
|
245
|
-
pruneRefreshTokens(uid: string, sessionId: string, supersededBefore: Date): Promise<void>;
|
|
246
|
-
getTokensValidAfter(uid: string): Promise<Date | null>;
|
|
247
|
-
setTokensValidAfter(uid: string, at: Date): Promise<void>;
|
|
208
|
+
createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
|
|
248
209
|
findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
|
|
249
210
|
deleteRefreshToken(tokenHash: string): Promise<void>;
|
|
250
211
|
deleteAllRefreshTokensForUser(uid: string): Promise<void>;
|
package/dist/index.es.js
CHANGED
|
@@ -5,7 +5,7 @@ import { Client, Pool } from "pg";
|
|
|
5
5
|
import { drizzle } from "drizzle-orm/node-postgres";
|
|
6
6
|
import { ApiError, createEmailService, extractUserFromToken, loadCollectionsFromDirectory, logger, safeCompare } from "@rebasepro/server";
|
|
7
7
|
import { and, asc, count, desc, eq, getTableColumns, getTableName, gt, ilike, inArray, isTable, lt, or, relations, sql } from "drizzle-orm";
|
|
8
|
-
import { PgArray, PgChar, PgTable, PgText, PgVarchar, bigint, boolean, char, cidr, customType, date, doublePrecision, geometry, getTableConfig,
|
|
8
|
+
import { PgArray, PgChar, PgTable, PgText, PgVarchar, bigint, boolean, char, cidr, customType, date, doublePrecision, geometry, getTableConfig, inet, integer, interval, json, jsonb, line, macaddr, macaddr8, numeric, pgSchema, pgTable, point, primaryKey, real, smallint, text, time, timestamp, unique, uuid, varchar, vector } from "drizzle-orm/pg-core";
|
|
9
9
|
import fs, { promises } from "fs";
|
|
10
10
|
import path from "path";
|
|
11
11
|
import chokidar from "chokidar";
|
|
@@ -9349,59 +9349,21 @@ function createAuthSchema(usersSchemaName = "rebase") {
|
|
|
9349
9349
|
isAnonymous: boolean("is_anonymous").default(false).notNull(),
|
|
9350
9350
|
roles: text("roles").array().default([]).notNull(),
|
|
9351
9351
|
metadata: jsonb("metadata").$type().default({}).notNull(),
|
|
9352
|
-
/**
|
|
9353
|
-
* Sessions that began before this instant are dead, whatever tokens
|
|
9354
|
-
* they still hold. Password resets and admin revocations stamp it.
|
|
9355
|
-
*
|
|
9356
|
-
* Deleting the user's refresh-token rows (which we also do) is not
|
|
9357
|
-
* sufficient on its own: a request already in flight can insert a
|
|
9358
|
-
* freshly rotated row microseconds after the delete and survive it.
|
|
9359
|
-
* This timestamp cannot be outrun that way — it is checked against
|
|
9360
|
-
* `refresh_tokens.session_started_at`, which rotation carries forward.
|
|
9361
|
-
*/
|
|
9362
|
-
tokensValidAfter: timestamp("tokens_valid_after"),
|
|
9363
9352
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
9364
9353
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
9365
9354
|
});
|
|
9366
9355
|
/**
|
|
9367
|
-
* Refresh tokens for long-lived sessions
|
|
9368
|
-
*
|
|
9369
|
-
* A row is one token, not one device. Every token minted from the same
|
|
9370
|
-
* sign-in shares a `sessionId`, and rotation ADDS a row rather than
|
|
9371
|
-
* replacing one: the superseded token stays on file, flagged `revoked`
|
|
9372
|
-
* with a `rotatedAt` stamp. That record is what lets the refresh endpoint
|
|
9373
|
-
* tell a client replaying a token it never got an answer for (a response
|
|
9374
|
-
* lost to a redeploy, a second tab racing on boot) apart from a stranger
|
|
9375
|
-
* presenting a token that was never issued. Deleting the old row on sight
|
|
9376
|
-
* — the previous behaviour — made those two cases indistinguishable, and
|
|
9377
|
-
* the legitimate one is overwhelmingly the common one.
|
|
9378
|
-
*
|
|
9379
|
-
* There is deliberately NO unique constraint on (uid, user_agent,
|
|
9380
|
-
* ip_address). Keying a session on the IP meant one row per "device",
|
|
9381
|
-
* so a second browser profile behind the same NAT silently evicted the
|
|
9382
|
-
* first, and a phone changing networks orphaned a row on every hop.
|
|
9383
|
-
* User agent and IP are descriptive metadata for the sessions list;
|
|
9384
|
-
* `sessionId` is the identity.
|
|
9356
|
+
* Refresh tokens for long-lived sessions
|
|
9385
9357
|
*/
|
|
9386
9358
|
const refreshTokens = tableCreator("refresh_tokens", {
|
|
9387
9359
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
9388
9360
|
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
9389
|
-
sessionId: uuid("session_id").defaultRandom().notNull(),
|
|
9390
9361
|
tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
|
|
9391
9362
|
expiresAt: timestamp("expires_at").notNull(),
|
|
9392
|
-
revoked: boolean("revoked").default(false).notNull(),
|
|
9393
|
-
rotatedAt: timestamp("rotated_at"),
|
|
9394
|
-
/**
|
|
9395
|
-
* When the sign-in this token descends from happened — carried across
|
|
9396
|
-
* every rotation, unlike `createdAt`. `users.tokensValidAfter` is
|
|
9397
|
-
* compared against this, so a revocation cannot be outrun by a token
|
|
9398
|
-
* that rotates immediately after it.
|
|
9399
|
-
*/
|
|
9400
|
-
sessionStartedAt: timestamp("session_started_at").defaultNow().notNull(),
|
|
9401
9363
|
userAgent: varchar("user_agent", { length: 500 }),
|
|
9402
9364
|
ipAddress: varchar("ip_address", { length: 45 }),
|
|
9403
9365
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
9404
|
-
}, (table) => ({
|
|
9366
|
+
}, (table) => ({ uniqueDeviceSession: unique("unique_device_session").on(table.uid, table.userAgent, table.ipAddress) }));
|
|
9405
9367
|
/**
|
|
9406
9368
|
* Password reset tokens for forgot password flow
|
|
9407
9369
|
*/
|
|
@@ -21191,7 +21153,6 @@ async function ensureAuthTablesExist(db, collection) {
|
|
|
21191
21153
|
email_verification_sent_at TIMESTAMP WITH TIME ZONE,
|
|
21192
21154
|
is_anonymous BOOLEAN DEFAULT FALSE NOT NULL,
|
|
21193
21155
|
metadata JSONB DEFAULT '{}' NOT NULL,
|
|
21194
|
-
tokens_valid_after TIMESTAMP WITH TIME ZONE,
|
|
21195
21156
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
|
21196
21157
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL
|
|
21197
21158
|
)
|
|
@@ -21272,15 +21233,12 @@ async function ensureAuthTablesExist(db, collection) {
|
|
|
21272
21233
|
CREATE TABLE IF NOT EXISTS ${sql.raw(refreshTokensTableName)} (
|
|
21273
21234
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
21274
21235
|
uid ${sql.raw(userIdType)} NOT NULL REFERENCES ${sql.raw(usersTableName)}(id) ON DELETE CASCADE,
|
|
21275
|
-
session_id TEXT NOT NULL DEFAULT gen_random_uuid()::text,
|
|
21276
21236
|
token_hash TEXT NOT NULL UNIQUE,
|
|
21277
21237
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
21278
|
-
revoked BOOLEAN DEFAULT FALSE NOT NULL,
|
|
21279
|
-
rotated_at TIMESTAMP WITH TIME ZONE,
|
|
21280
|
-
session_started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,
|
|
21281
21238
|
user_agent TEXT,
|
|
21282
21239
|
ip_address TEXT,
|
|
21283
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
21240
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
21241
|
+
CONSTRAINT unique_device_session UNIQUE (uid, user_agent, ip_address)
|
|
21284
21242
|
)
|
|
21285
21243
|
`);
|
|
21286
21244
|
await db.execute(sql`
|
|
@@ -21370,7 +21328,6 @@ async function ensureAuthTablesExist(db, collection) {
|
|
|
21370
21328
|
"email_verification_sent_at TIMESTAMP WITH TIME ZONE",
|
|
21371
21329
|
"is_anonymous BOOLEAN DEFAULT FALSE NOT NULL",
|
|
21372
21330
|
"metadata JSONB DEFAULT '{}' NOT NULL",
|
|
21373
|
-
"tokens_valid_after TIMESTAMP WITH TIME ZONE",
|
|
21374
21331
|
"created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL",
|
|
21375
21332
|
"updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL"
|
|
21376
21333
|
]) await db.execute(sql`
|
|
@@ -21387,36 +21344,42 @@ async function ensureAuthTablesExist(db, collection) {
|
|
|
21387
21344
|
for (const { table_schema } of found) {
|
|
21388
21345
|
const qualified = `"${table_schema}"."refresh_tokens"`;
|
|
21389
21346
|
try {
|
|
21390
|
-
await db.execute(sql`ALTER TABLE ${sql.raw(qualified)} ADD COLUMN IF NOT EXISTS
|
|
21391
|
-
await db.execute(sql`ALTER TABLE ${sql.raw(qualified)} ADD COLUMN IF NOT EXISTS
|
|
21392
|
-
await db.execute(sql`
|
|
21393
|
-
await db.execute(sql`
|
|
21394
|
-
await db.execute(sql`
|
|
21395
|
-
UPDATE ${sql.raw(qualified)}
|
|
21396
|
-
SET session_id = gen_random_uuid()::text
|
|
21397
|
-
WHERE session_id IS NULL
|
|
21398
|
-
`);
|
|
21347
|
+
await db.execute(sql`ALTER TABLE ${sql.raw(qualified)} ADD COLUMN IF NOT EXISTS user_agent TEXT`);
|
|
21348
|
+
await db.execute(sql`ALTER TABLE ${sql.raw(qualified)} ADD COLUMN IF NOT EXISTS ip_address TEXT`);
|
|
21349
|
+
await db.execute(sql`UPDATE ${sql.raw(qualified)} SET user_agent = '' WHERE user_agent IS NULL`);
|
|
21350
|
+
await db.execute(sql`UPDATE ${sql.raw(qualified)} SET ip_address = '' WHERE ip_address IS NULL`);
|
|
21399
21351
|
await db.execute(sql`
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
WHERE
|
|
21352
|
+
DELETE FROM ${sql.raw(qualified)} a
|
|
21353
|
+
USING ${sql.raw(qualified)} b
|
|
21354
|
+
WHERE a.ctid < b.ctid
|
|
21355
|
+
AND a.uid = b.uid
|
|
21356
|
+
AND a.user_agent = b.user_agent
|
|
21357
|
+
AND a.ip_address = b.ip_address
|
|
21403
21358
|
`);
|
|
21404
|
-
await db.execute(sql`
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
await db.execute(sql`
|
|
21409
|
-
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_session
|
|
21410
|
-
ON ${sql.raw(qualified)}(session_id)
|
|
21359
|
+
const uniques = await db.execute(sql`
|
|
21360
|
+
SELECT conname, pg_get_constraintdef(oid) AS def
|
|
21361
|
+
FROM pg_constraint
|
|
21362
|
+
WHERE conrelid = ${sql.raw(`'${qualified}'`)}::regclass AND contype = 'u'
|
|
21411
21363
|
`);
|
|
21412
|
-
|
|
21413
|
-
|
|
21364
|
+
for (const u of uniques.rows) logger.info(` ${qualified} unique: ${u.conname} → ${u.def}`);
|
|
21365
|
+
if (uniques.rows.some((u) => {
|
|
21366
|
+
const cols = (u.def.match(/\(([^)]*)\)/)?.[1] || "").split(",").map((c) => c.trim().replace(/"/g, ""));
|
|
21367
|
+
return cols.length === 3 && cols.includes("uid") && cols.includes("user_agent") && cols.includes("ip_address");
|
|
21368
|
+
})) logger.info(`✓ correct device-session unique already present on ${qualified}`);
|
|
21369
|
+
else {
|
|
21370
|
+
await db.execute(sql`ALTER TABLE ${sql.raw(qualified)} DROP CONSTRAINT IF EXISTS unique_device_session`);
|
|
21371
|
+
await db.execute(sql`
|
|
21372
|
+
ALTER TABLE ${sql.raw(qualified)}
|
|
21373
|
+
ADD CONSTRAINT unique_device_session UNIQUE (uid, user_agent, ip_address)
|
|
21374
|
+
`);
|
|
21375
|
+
logger.info(`✅ Added correct unique_device_session constraint to ${qualified}`);
|
|
21376
|
+
}
|
|
21414
21377
|
} catch (perTableError) {
|
|
21415
21378
|
logger.warn(`⚠️ refresh_tokens reconcile failed for ${qualified}: ${perTableError instanceof Error ? perTableError.message : String(perTableError)}`);
|
|
21416
21379
|
}
|
|
21417
21380
|
}
|
|
21418
21381
|
} catch (migrationError) {
|
|
21419
|
-
logger.warn(`⚠️ refresh_tokens session migration skipped: ${migrationError instanceof Error ? migrationError.message : String(migrationError)}`);
|
|
21382
|
+
logger.warn(`⚠️ refresh_tokens device-session migration skipped: ${migrationError instanceof Error ? migrationError.message : String(migrationError)}`);
|
|
21420
21383
|
}
|
|
21421
21384
|
try {
|
|
21422
21385
|
if ((await db.execute(sql`
|
|
@@ -21908,31 +21871,34 @@ var UserService = class {
|
|
|
21908
21871
|
var RefreshTokenService = class {
|
|
21909
21872
|
db;
|
|
21910
21873
|
refreshTokensTable;
|
|
21911
|
-
usersTable;
|
|
21912
21874
|
constructor(db, tableOrTables) {
|
|
21913
21875
|
this.db = db;
|
|
21914
|
-
if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users))
|
|
21915
|
-
|
|
21916
|
-
this.usersTable = tableOrTables.users || users;
|
|
21917
|
-
} else {
|
|
21918
|
-
this.refreshTokensTable = tableOrTables || refreshTokens;
|
|
21919
|
-
this.usersTable = users;
|
|
21920
|
-
}
|
|
21876
|
+
if (tableOrTables && (tableOrTables.refreshTokens || tableOrTables.users)) this.refreshTokensTable = tableOrTables.refreshTokens || refreshTokens;
|
|
21877
|
+
else this.refreshTokensTable = tableOrTables || refreshTokens;
|
|
21921
21878
|
}
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21879
|
+
async createToken(uid, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
21880
|
+
const safeUserAgent = userAgent || "";
|
|
21881
|
+
const safeIpAddress = ipAddress || "";
|
|
21882
|
+
await this.db.insert(this.refreshTokensTable).values({
|
|
21883
|
+
uid,
|
|
21884
|
+
tokenHash,
|
|
21885
|
+
expiresAt,
|
|
21886
|
+
userAgent: safeUserAgent,
|
|
21887
|
+
ipAddress: safeIpAddress
|
|
21888
|
+
}).onConflictDoUpdate({
|
|
21889
|
+
target: [
|
|
21890
|
+
this.refreshTokensTable.uid,
|
|
21891
|
+
this.refreshTokensTable.userAgent,
|
|
21892
|
+
this.refreshTokensTable.ipAddress
|
|
21893
|
+
],
|
|
21894
|
+
set: {
|
|
21895
|
+
tokenHash,
|
|
21896
|
+
expiresAt
|
|
21897
|
+
}
|
|
21898
|
+
});
|
|
21932
21899
|
}
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
const selection = {
|
|
21900
|
+
async findByHash(tokenHash) {
|
|
21901
|
+
const [token] = await this.db.select({
|
|
21936
21902
|
id: this.refreshTokensTable.id,
|
|
21937
21903
|
uid: this.refreshTokensTable.uid,
|
|
21938
21904
|
tokenHash: this.refreshTokensTable.tokenHash,
|
|
@@ -21940,94 +21906,9 @@ var RefreshTokenService = class {
|
|
|
21940
21906
|
createdAt: this.refreshTokensTable.createdAt,
|
|
21941
21907
|
userAgent: this.refreshTokensTable.userAgent,
|
|
21942
21908
|
ipAddress: this.refreshTokensTable.ipAddress
|
|
21943
|
-
};
|
|
21944
|
-
for (const optional of [
|
|
21945
|
-
"sessionId",
|
|
21946
|
-
"rotatedAt",
|
|
21947
|
-
"revoked",
|
|
21948
|
-
"sessionStartedAt"
|
|
21949
|
-
]) if (this.has(optional)) selection[optional] = this.col(optional);
|
|
21950
|
-
return selection;
|
|
21951
|
-
}
|
|
21952
|
-
async createToken(uid, tokenHash, expiresAt, userAgent, ipAddress, session) {
|
|
21953
|
-
const values = {
|
|
21954
|
-
uid,
|
|
21955
|
-
tokenHash,
|
|
21956
|
-
expiresAt,
|
|
21957
|
-
userAgent: userAgent || "",
|
|
21958
|
-
ipAddress: ipAddress || ""
|
|
21959
|
-
};
|
|
21960
|
-
if (session && this.has("sessionId")) values.sessionId = session.id;
|
|
21961
|
-
if (session && this.has("sessionStartedAt")) values.sessionStartedAt = session.startedAt;
|
|
21962
|
-
await this.db.insert(this.refreshTokensTable).values(values);
|
|
21963
|
-
}
|
|
21964
|
-
async findByHash(tokenHash) {
|
|
21965
|
-
const [token] = await this.db.select(this.selection()).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
21909
|
+
}).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
21966
21910
|
return token || null;
|
|
21967
21911
|
}
|
|
21968
|
-
/**
|
|
21969
|
-
* Record that a token was rotated away, keeping the row.
|
|
21970
|
-
*
|
|
21971
|
-
* The row is what lets `/auth/refresh` distinguish "you already used this,
|
|
21972
|
-
* here is a fresh one" from "no idea what this is". Deleting it — which is
|
|
21973
|
-
* what this used to do — collapsed both into a 401 and signed the user out
|
|
21974
|
-
* for the crime of losing a response.
|
|
21975
|
-
*/
|
|
21976
|
-
async markRotated(tokenHash) {
|
|
21977
|
-
if (!this.has("rotatedAt")) {
|
|
21978
|
-
await this.deleteByHash(tokenHash);
|
|
21979
|
-
return;
|
|
21980
|
-
}
|
|
21981
|
-
await this.db.update(this.refreshTokensTable).set({ rotatedAt: /* @__PURE__ */ new Date() }).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
21982
|
-
}
|
|
21983
|
-
/** Final kill of one sign-in: logout, or revoking a device remotely. */
|
|
21984
|
-
async revokeSession(sessionId) {
|
|
21985
|
-
if (!this.has("sessionId")) return;
|
|
21986
|
-
if (this.has("revoked")) {
|
|
21987
|
-
await this.db.update(this.refreshTokensTable).set({
|
|
21988
|
-
revoked: true,
|
|
21989
|
-
...this.has("rotatedAt") ? { rotatedAt: /* @__PURE__ */ new Date() } : {}
|
|
21990
|
-
}).where(eq(this.col("sessionId"), sessionId));
|
|
21991
|
-
return;
|
|
21992
|
-
}
|
|
21993
|
-
await this.db.delete(this.refreshTokensTable).where(eq(this.col("sessionId"), sessionId));
|
|
21994
|
-
}
|
|
21995
|
-
/**
|
|
21996
|
-
* Housekeeping: rotation would otherwise leave a row per refresh forever.
|
|
21997
|
-
* Superseded rows are only needed for as long as a straggler might still
|
|
21998
|
-
* present them, and expired ones are dead weight everywhere.
|
|
21999
|
-
*/
|
|
22000
|
-
async prune(uid, sessionId, supersededBefore) {
|
|
22001
|
-
const uidCol = this.refreshTokensTable.uid;
|
|
22002
|
-
const expiresCol = this.refreshTokensTable.expiresAt;
|
|
22003
|
-
if (!this.has("rotatedAt") || !this.has("sessionId")) {
|
|
22004
|
-
await this.db.delete(this.refreshTokensTable).where(sql`${uidCol} = ${uid} AND ${expiresCol} < NOW()`);
|
|
22005
|
-
return;
|
|
22006
|
-
}
|
|
22007
|
-
const rotatedCol = this.col("rotatedAt");
|
|
22008
|
-
const sessionCol = this.col("sessionId");
|
|
22009
|
-
await this.db.delete(this.refreshTokensTable).where(sql`
|
|
22010
|
-
${uidCol} = ${uid}
|
|
22011
|
-
AND (
|
|
22012
|
-
${expiresCol} < NOW()
|
|
22013
|
-
OR (
|
|
22014
|
-
${sessionCol} = ${sessionId}
|
|
22015
|
-
AND ${rotatedCol} IS NOT NULL
|
|
22016
|
-
AND ${rotatedCol} < ${supersededBefore}
|
|
22017
|
-
)
|
|
22018
|
-
)
|
|
22019
|
-
`);
|
|
22020
|
-
}
|
|
22021
|
-
async getTokensValidAfter(uid) {
|
|
22022
|
-
if (!this.usersTable || !this.usersTable.tokensValidAfter) return null;
|
|
22023
|
-
const [row] = await this.db.select({ tokensValidAfter: this.usersTable.tokensValidAfter }).from(this.usersTable).where(eq(this.usersTable.id, uid));
|
|
22024
|
-
const value = row?.tokensValidAfter;
|
|
22025
|
-
return value ? new Date(value) : null;
|
|
22026
|
-
}
|
|
22027
|
-
async setTokensValidAfter(uid, at) {
|
|
22028
|
-
if (!this.usersTable || !this.usersTable.tokensValidAfter) return;
|
|
22029
|
-
await this.db.update(this.usersTable).set({ tokensValidAfter: at }).where(eq(this.usersTable.id, uid));
|
|
22030
|
-
}
|
|
22031
21912
|
async deleteByHash(tokenHash) {
|
|
22032
21913
|
await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.tokenHash, tokenHash));
|
|
22033
21914
|
}
|
|
@@ -22035,7 +21916,15 @@ var RefreshTokenService = class {
|
|
|
22035
21916
|
await this.db.delete(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid));
|
|
22036
21917
|
}
|
|
22037
21918
|
async listForUser(uid) {
|
|
22038
|
-
return await this.db.select(
|
|
21919
|
+
return await this.db.select({
|
|
21920
|
+
id: this.refreshTokensTable.id,
|
|
21921
|
+
uid: this.refreshTokensTable.uid,
|
|
21922
|
+
tokenHash: this.refreshTokensTable.tokenHash,
|
|
21923
|
+
expiresAt: this.refreshTokensTable.expiresAt,
|
|
21924
|
+
createdAt: this.refreshTokensTable.createdAt,
|
|
21925
|
+
userAgent: this.refreshTokensTable.userAgent,
|
|
21926
|
+
ipAddress: this.refreshTokensTable.ipAddress
|
|
21927
|
+
}).from(this.refreshTokensTable).where(eq(this.refreshTokensTable.uid, uid)).orderBy(this.refreshTokensTable.createdAt);
|
|
22039
21928
|
}
|
|
22040
21929
|
async deleteById(id, uid) {
|
|
22041
21930
|
await this.db.delete(this.refreshTokensTable).where(sql`${this.refreshTokensTable.id} = ${id} AND ${this.refreshTokensTable.uid} = ${uid}`);
|
|
@@ -22180,23 +22069,8 @@ var PostgresTokenRepository = class {
|
|
|
22180
22069
|
this.passwordResetTokenService = new PasswordResetTokenService(db, tableOrTables);
|
|
22181
22070
|
this.magicLinkTokenService = new MagicLinkTokenService(db, tableOrTables);
|
|
22182
22071
|
}
|
|
22183
|
-
async createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress
|
|
22184
|
-
await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress
|
|
22185
|
-
}
|
|
22186
|
-
async markRefreshTokenRotated(tokenHash) {
|
|
22187
|
-
await this.refreshTokenService.markRotated(tokenHash);
|
|
22188
|
-
}
|
|
22189
|
-
async revokeRefreshTokenSession(sessionId) {
|
|
22190
|
-
await this.refreshTokenService.revokeSession(sessionId);
|
|
22191
|
-
}
|
|
22192
|
-
async pruneRefreshTokens(uid, sessionId, supersededBefore) {
|
|
22193
|
-
await this.refreshTokenService.prune(uid, sessionId, supersededBefore);
|
|
22194
|
-
}
|
|
22195
|
-
async getTokensValidAfter(uid) {
|
|
22196
|
-
return this.refreshTokenService.getTokensValidAfter(uid);
|
|
22197
|
-
}
|
|
22198
|
-
async setTokensValidAfter(uid, at) {
|
|
22199
|
-
await this.refreshTokenService.setTokensValidAfter(uid, at);
|
|
22072
|
+
async createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
22073
|
+
await this.refreshTokenService.createToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
|
|
22200
22074
|
}
|
|
22201
22075
|
async findRefreshTokenByHash(tokenHash) {
|
|
22202
22076
|
return this.refreshTokenService.findByHash(tokenHash);
|
|
@@ -22362,23 +22236,8 @@ var PostgresAuthRepository = class {
|
|
|
22362
22236
|
};
|
|
22363
22237
|
}
|
|
22364
22238
|
async deleteRole(_id) {}
|
|
22365
|
-
async createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress
|
|
22366
|
-
await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress
|
|
22367
|
-
}
|
|
22368
|
-
async markRefreshTokenRotated(tokenHash) {
|
|
22369
|
-
await this.tokenRepository.markRefreshTokenRotated(tokenHash);
|
|
22370
|
-
}
|
|
22371
|
-
async revokeRefreshTokenSession(sessionId) {
|
|
22372
|
-
await this.tokenRepository.revokeRefreshTokenSession(sessionId);
|
|
22373
|
-
}
|
|
22374
|
-
async pruneRefreshTokens(uid, sessionId, supersededBefore) {
|
|
22375
|
-
await this.tokenRepository.pruneRefreshTokens(uid, sessionId, supersededBefore);
|
|
22376
|
-
}
|
|
22377
|
-
async getTokensValidAfter(uid) {
|
|
22378
|
-
return this.tokenRepository.getTokensValidAfter(uid);
|
|
22379
|
-
}
|
|
22380
|
-
async setTokensValidAfter(uid, at) {
|
|
22381
|
-
await this.tokenRepository.setTokensValidAfter(uid, at);
|
|
22239
|
+
async createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress) {
|
|
22240
|
+
await this.tokenRepository.createRefreshToken(uid, tokenHash, expiresAt, userAgent, ipAddress);
|
|
22382
22241
|
}
|
|
22383
22242
|
async findRefreshTokenByHash(tokenHash) {
|
|
22384
22243
|
return this.tokenRepository.findRefreshTokenByHash(tokenHash);
|