@rebasepro/server-postgres 0.10.0 → 0.10.1-canary.18115ba
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 +43 -4
- package/dist/index.es.js +231 -43
- package/dist/index.es.js.map +1 -1
- package/dist/schema/auth-schema.d.ts +170 -0
- package/package.json +6 -6
- package/src/auth/ensure-tables.ts +91 -3
- package/src/auth/services.ts +186 -48
- package/src/schema/auth-schema.ts +41 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pgSchema, pgTable, varchar, uuid, timestamp, boolean, jsonb, text, unique } from "drizzle-orm/pg-core";
|
|
1
|
+
import { pgSchema, pgTable, varchar, uuid, timestamp, boolean, jsonb, text, unique, index } from "drizzle-orm/pg-core";
|
|
2
2
|
import { relations } from "drizzle-orm";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -25,24 +25,62 @@ export function createAuthSchema(usersSchemaName = "rebase") {
|
|
|
25
25
|
isAnonymous: boolean("is_anonymous").default(false).notNull(),
|
|
26
26
|
roles: text("roles").array().default([]).notNull(),
|
|
27
27
|
metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}).notNull(),
|
|
28
|
+
/**
|
|
29
|
+
* Sessions that began before this instant are dead, whatever tokens
|
|
30
|
+
* they still hold. Password resets and admin revocations stamp it.
|
|
31
|
+
*
|
|
32
|
+
* Deleting the user's refresh-token rows (which we also do) is not
|
|
33
|
+
* sufficient on its own: a request already in flight can insert a
|
|
34
|
+
* freshly rotated row microseconds after the delete and survive it.
|
|
35
|
+
* This timestamp cannot be outrun that way — it is checked against
|
|
36
|
+
* `refresh_tokens.session_started_at`, which rotation carries forward.
|
|
37
|
+
*/
|
|
38
|
+
tokensValidAfter: timestamp("tokens_valid_after"),
|
|
28
39
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
29
40
|
updatedAt: timestamp("updated_at").defaultNow().notNull()
|
|
30
41
|
});
|
|
31
42
|
|
|
32
43
|
|
|
33
44
|
/**
|
|
34
|
-
* Refresh tokens for long-lived sessions
|
|
45
|
+
* Refresh tokens for long-lived sessions.
|
|
46
|
+
*
|
|
47
|
+
* A row is one token, not one device. Every token minted from the same
|
|
48
|
+
* sign-in shares a `sessionId`, and rotation ADDS a row rather than
|
|
49
|
+
* replacing one: the superseded token stays on file, flagged `revoked`
|
|
50
|
+
* with a `rotatedAt` stamp. That record is what lets the refresh endpoint
|
|
51
|
+
* tell a client replaying a token it never got an answer for (a response
|
|
52
|
+
* lost to a redeploy, a second tab racing on boot) apart from a stranger
|
|
53
|
+
* presenting a token that was never issued. Deleting the old row on sight
|
|
54
|
+
* — the previous behaviour — made those two cases indistinguishable, and
|
|
55
|
+
* the legitimate one is overwhelmingly the common one.
|
|
56
|
+
*
|
|
57
|
+
* There is deliberately NO unique constraint on (uid, user_agent,
|
|
58
|
+
* ip_address). Keying a session on the IP meant one row per "device",
|
|
59
|
+
* so a second browser profile behind the same NAT silently evicted the
|
|
60
|
+
* first, and a phone changing networks orphaned a row on every hop.
|
|
61
|
+
* User agent and IP are descriptive metadata for the sessions list;
|
|
62
|
+
* `sessionId` is the identity.
|
|
35
63
|
*/
|
|
36
64
|
const refreshTokens = tableCreator("refresh_tokens", {
|
|
37
65
|
id: uuid("id").defaultRandom().primaryKey(),
|
|
38
66
|
uid: uuid("uid").notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
67
|
+
sessionId: uuid("session_id").defaultRandom().notNull(),
|
|
39
68
|
tokenHash: varchar("token_hash", { length: 255 }).notNull().unique(),
|
|
40
69
|
expiresAt: timestamp("expires_at").notNull(),
|
|
70
|
+
revoked: boolean("revoked").default(false).notNull(),
|
|
71
|
+
rotatedAt: timestamp("rotated_at"),
|
|
72
|
+
/**
|
|
73
|
+
* When the sign-in this token descends from happened — carried across
|
|
74
|
+
* every rotation, unlike `createdAt`. `users.tokensValidAfter` is
|
|
75
|
+
* compared against this, so a revocation cannot be outrun by a token
|
|
76
|
+
* that rotates immediately after it.
|
|
77
|
+
*/
|
|
78
|
+
sessionStartedAt: timestamp("session_started_at").defaultNow().notNull(),
|
|
41
79
|
userAgent: varchar("user_agent", { length: 500 }),
|
|
42
80
|
ipAddress: varchar("ip_address", { length: 45 }),
|
|
43
81
|
createdAt: timestamp("created_at").defaultNow().notNull()
|
|
44
82
|
}, (table) => ({
|
|
45
|
-
|
|
83
|
+
sessionIdx: index("idx_refresh_tokens_session").on(table.sessionId)
|
|
46
84
|
}));
|
|
47
85
|
|
|
48
86
|
/**
|