@nxgt/janus-drizzle 0.3.2 → 0.4.0
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/README.md +9 -1
- package/dist/index.js +378 -303
- package/dist/index.js.map +20 -6
- package/dist/relations/context.d.ts +9 -0
- package/dist/relations/context.d.ts.map +1 -0
- package/dist/{relations.d.ts → relations/index.d.ts} +3 -3
- package/dist/relations/index.d.ts.map +1 -0
- package/dist/relations/reads.d.ts +9 -0
- package/dist/relations/reads.d.ts.map +1 -0
- package/dist/relations/tuples.d.ts +12 -0
- package/dist/relations/tuples.d.ts.map +1 -0
- package/dist/relations/writes.d.ts +7 -0
- package/dist/relations/writes.d.ts.map +1 -0
- package/dist/stores/identity-tables.d.ts +5 -0
- package/dist/stores/identity-tables.d.ts.map +1 -0
- package/dist/{stores.d.ts → stores/index.d.ts} +4 -6
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/logins.d.ts +13 -0
- package/dist/stores/logins.d.ts.map +1 -0
- package/dist/stores/records.d.ts +23 -0
- package/dist/stores/records.d.ts.map +1 -0
- package/dist/stores/sessions.d.ts +5 -0
- package/dist/stores/sessions.d.ts.map +1 -0
- package/dist/stores/tokens.d.ts +5 -0
- package/dist/stores/tokens.d.ts.map +1 -0
- package/dist/stores/users.d.ts +5 -0
- package/dist/stores/users.d.ts.map +1 -0
- package/dist/tables/columns.d.ts +22 -0
- package/dist/tables/columns.d.ts.map +1 -0
- package/dist/{tables.d.ts → tables/index.d.ts} +16 -1
- package/dist/tables/index.d.ts.map +1 -0
- package/dist/tables/logins.d.ts +74 -0
- package/dist/tables/logins.d.ts.map +1 -0
- package/dist/tables/relations.d.ts +133 -0
- package/dist/tables/relations.d.ts.map +1 -0
- package/dist/tables/sessions.d.ts +131 -0
- package/dist/tables/sessions.d.ts.map +1 -0
- package/dist/tables/tokens.d.ts +153 -0
- package/dist/tables/tokens.d.ts.map +1 -0
- package/dist/tables/users.d.ts +276 -0
- package/dist/tables/users.d.ts.map +1 -0
- package/docs/guide/migrations.md +35 -4
- package/docs/roadmap.md +6 -0
- package/docs/troubleshooting.md +21 -0
- package/package.json +3 -3
- package/dist/relations.d.ts.map +0 -1
- package/dist/stores.d.ts.map +0 -1
- package/dist/tables.d.ts.map +0 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { type TableFactory } from './columns';
|
|
2
|
+
/**
|
|
3
|
+
* Users and their passwords: one row each. `logins` is the port's array,
|
|
4
|
+
* verbatim and in order; its uniqueness is `logins`' primary key.
|
|
5
|
+
*/
|
|
6
|
+
export declare function usersTable(table: TableFactory): import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
7
|
+
name: "users";
|
|
8
|
+
schema: undefined;
|
|
9
|
+
columns: {
|
|
10
|
+
id: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetIsPrimaryKey<import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
11
|
+
dataType: "custom";
|
|
12
|
+
data: string;
|
|
13
|
+
driverParam: string;
|
|
14
|
+
}>>, {
|
|
15
|
+
name: string;
|
|
16
|
+
tableName: "users";
|
|
17
|
+
dataType: "custom";
|
|
18
|
+
data: string;
|
|
19
|
+
driverParam: string;
|
|
20
|
+
notNull: true;
|
|
21
|
+
hasDefault: false;
|
|
22
|
+
isPrimaryKey: false;
|
|
23
|
+
isAutoincrement: false;
|
|
24
|
+
hasRuntimeDefault: false;
|
|
25
|
+
enumValues: undefined;
|
|
26
|
+
identity: undefined;
|
|
27
|
+
generated: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
type: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgCustomColumnBuilder<{
|
|
30
|
+
dataType: "custom";
|
|
31
|
+
data: string;
|
|
32
|
+
driverParam: string;
|
|
33
|
+
}>>, {
|
|
34
|
+
name: string;
|
|
35
|
+
tableName: "users";
|
|
36
|
+
dataType: "custom";
|
|
37
|
+
data: string;
|
|
38
|
+
driverParam: string;
|
|
39
|
+
notNull: true;
|
|
40
|
+
hasDefault: false;
|
|
41
|
+
isPrimaryKey: false;
|
|
42
|
+
isAutoincrement: false;
|
|
43
|
+
hasRuntimeDefault: false;
|
|
44
|
+
enumValues: undefined;
|
|
45
|
+
identity: undefined;
|
|
46
|
+
generated: undefined;
|
|
47
|
+
}>;
|
|
48
|
+
schemaVersion: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>>, {
|
|
49
|
+
name: string;
|
|
50
|
+
tableName: "users";
|
|
51
|
+
dataType: "string";
|
|
52
|
+
data: string;
|
|
53
|
+
driverParam: string;
|
|
54
|
+
notNull: true;
|
|
55
|
+
hasDefault: false;
|
|
56
|
+
isPrimaryKey: false;
|
|
57
|
+
isAutoincrement: false;
|
|
58
|
+
hasRuntimeDefault: false;
|
|
59
|
+
enumValues: undefined;
|
|
60
|
+
identity: undefined;
|
|
61
|
+
generated: undefined;
|
|
62
|
+
}>;
|
|
63
|
+
active: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgBooleanBuilder>, {
|
|
64
|
+
name: string;
|
|
65
|
+
tableName: "users";
|
|
66
|
+
dataType: "boolean";
|
|
67
|
+
data: boolean;
|
|
68
|
+
driverParam: boolean;
|
|
69
|
+
notNull: true;
|
|
70
|
+
hasDefault: false;
|
|
71
|
+
isPrimaryKey: false;
|
|
72
|
+
isAutoincrement: false;
|
|
73
|
+
hasRuntimeDefault: false;
|
|
74
|
+
enumValues: undefined;
|
|
75
|
+
identity: undefined;
|
|
76
|
+
generated: undefined;
|
|
77
|
+
}>;
|
|
78
|
+
fields: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgJsonbBuilder>, {
|
|
79
|
+
name: string;
|
|
80
|
+
tableName: "users";
|
|
81
|
+
dataType: "object json";
|
|
82
|
+
data: unknown;
|
|
83
|
+
driverParam: unknown;
|
|
84
|
+
notNull: true;
|
|
85
|
+
hasDefault: false;
|
|
86
|
+
isPrimaryKey: false;
|
|
87
|
+
isAutoincrement: false;
|
|
88
|
+
hasRuntimeDefault: false;
|
|
89
|
+
enumValues: undefined;
|
|
90
|
+
identity: undefined;
|
|
91
|
+
generated: undefined;
|
|
92
|
+
}>;
|
|
93
|
+
logins: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").SetDimensions<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, 1>>, {
|
|
94
|
+
name: string;
|
|
95
|
+
tableName: "users";
|
|
96
|
+
dataType: "string";
|
|
97
|
+
data: string[];
|
|
98
|
+
driverParam: string | string[];
|
|
99
|
+
notNull: true;
|
|
100
|
+
hasDefault: false;
|
|
101
|
+
isPrimaryKey: false;
|
|
102
|
+
isAutoincrement: false;
|
|
103
|
+
hasRuntimeDefault: false;
|
|
104
|
+
enumValues: undefined;
|
|
105
|
+
identity: undefined;
|
|
106
|
+
generated: undefined;
|
|
107
|
+
}>;
|
|
108
|
+
passwordHash: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
|
|
109
|
+
name: string;
|
|
110
|
+
tableName: "users";
|
|
111
|
+
dataType: "string";
|
|
112
|
+
data: string;
|
|
113
|
+
driverParam: string;
|
|
114
|
+
notNull: false;
|
|
115
|
+
hasDefault: false;
|
|
116
|
+
isPrimaryKey: false;
|
|
117
|
+
isAutoincrement: false;
|
|
118
|
+
hasRuntimeDefault: false;
|
|
119
|
+
enumValues: undefined;
|
|
120
|
+
identity: undefined;
|
|
121
|
+
generated: undefined;
|
|
122
|
+
}>;
|
|
123
|
+
passwordUpdatedAt: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgTimestampBuilder, {
|
|
124
|
+
name: string;
|
|
125
|
+
tableName: "users";
|
|
126
|
+
dataType: "object date";
|
|
127
|
+
data: Date;
|
|
128
|
+
driverParam: string;
|
|
129
|
+
notNull: false;
|
|
130
|
+
hasDefault: false;
|
|
131
|
+
isPrimaryKey: false;
|
|
132
|
+
isAutoincrement: false;
|
|
133
|
+
hasRuntimeDefault: false;
|
|
134
|
+
enumValues: undefined;
|
|
135
|
+
identity: undefined;
|
|
136
|
+
generated: undefined;
|
|
137
|
+
}>;
|
|
138
|
+
secondFactorMethod: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgTextBuilder<["totp"]>, {
|
|
139
|
+
name: string;
|
|
140
|
+
tableName: "users";
|
|
141
|
+
dataType: "string enum";
|
|
142
|
+
data: "totp";
|
|
143
|
+
driverParam: string;
|
|
144
|
+
notNull: false;
|
|
145
|
+
hasDefault: false;
|
|
146
|
+
isPrimaryKey: false;
|
|
147
|
+
isAutoincrement: false;
|
|
148
|
+
hasRuntimeDefault: false;
|
|
149
|
+
enumValues: ["totp"];
|
|
150
|
+
identity: undefined;
|
|
151
|
+
generated: undefined;
|
|
152
|
+
}>;
|
|
153
|
+
secondFactorSecret: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, {
|
|
154
|
+
name: string;
|
|
155
|
+
tableName: "users";
|
|
156
|
+
dataType: "string";
|
|
157
|
+
data: string;
|
|
158
|
+
driverParam: string;
|
|
159
|
+
notNull: false;
|
|
160
|
+
hasDefault: false;
|
|
161
|
+
isPrimaryKey: false;
|
|
162
|
+
isAutoincrement: false;
|
|
163
|
+
hasRuntimeDefault: false;
|
|
164
|
+
enumValues: undefined;
|
|
165
|
+
identity: undefined;
|
|
166
|
+
generated: undefined;
|
|
167
|
+
}>;
|
|
168
|
+
secondFactorConfirmedAt: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgTimestampBuilder, {
|
|
169
|
+
name: string;
|
|
170
|
+
tableName: "users";
|
|
171
|
+
dataType: "object date";
|
|
172
|
+
data: Date;
|
|
173
|
+
driverParam: string;
|
|
174
|
+
notNull: false;
|
|
175
|
+
hasDefault: false;
|
|
176
|
+
isPrimaryKey: false;
|
|
177
|
+
isAutoincrement: false;
|
|
178
|
+
hasRuntimeDefault: false;
|
|
179
|
+
enumValues: undefined;
|
|
180
|
+
identity: undefined;
|
|
181
|
+
generated: undefined;
|
|
182
|
+
}>;
|
|
183
|
+
secondFactorLastStep: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgIntegerBuilder, {
|
|
184
|
+
name: string;
|
|
185
|
+
tableName: "users";
|
|
186
|
+
dataType: "number int32";
|
|
187
|
+
data: number;
|
|
188
|
+
driverParam: string | number;
|
|
189
|
+
notNull: false;
|
|
190
|
+
hasDefault: false;
|
|
191
|
+
isPrimaryKey: false;
|
|
192
|
+
isAutoincrement: false;
|
|
193
|
+
hasRuntimeDefault: false;
|
|
194
|
+
enumValues: undefined;
|
|
195
|
+
identity: undefined;
|
|
196
|
+
generated: undefined;
|
|
197
|
+
}>;
|
|
198
|
+
secondFactorRecoveryCodes: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetDimensions<import("drizzle-orm/pg-core").PgTextBuilder<[string, ...string[]]>, 1>, {
|
|
199
|
+
name: string;
|
|
200
|
+
tableName: "users";
|
|
201
|
+
dataType: "string";
|
|
202
|
+
data: string[];
|
|
203
|
+
driverParam: string | string[];
|
|
204
|
+
notNull: false;
|
|
205
|
+
hasDefault: false;
|
|
206
|
+
isPrimaryKey: false;
|
|
207
|
+
isAutoincrement: false;
|
|
208
|
+
hasRuntimeDefault: false;
|
|
209
|
+
enumValues: undefined;
|
|
210
|
+
identity: undefined;
|
|
211
|
+
generated: undefined;
|
|
212
|
+
}>;
|
|
213
|
+
emailVerifiedAt: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").PgTimestampBuilder, {
|
|
214
|
+
name: string;
|
|
215
|
+
tableName: "users";
|
|
216
|
+
dataType: "object date";
|
|
217
|
+
data: Date;
|
|
218
|
+
driverParam: string;
|
|
219
|
+
notNull: false;
|
|
220
|
+
hasDefault: false;
|
|
221
|
+
isPrimaryKey: false;
|
|
222
|
+
isAutoincrement: false;
|
|
223
|
+
hasRuntimeDefault: false;
|
|
224
|
+
enumValues: undefined;
|
|
225
|
+
identity: undefined;
|
|
226
|
+
generated: undefined;
|
|
227
|
+
}>;
|
|
228
|
+
version: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgIntegerBuilder>, {
|
|
229
|
+
name: string;
|
|
230
|
+
tableName: "users";
|
|
231
|
+
dataType: "number int32";
|
|
232
|
+
data: number;
|
|
233
|
+
driverParam: string | number;
|
|
234
|
+
notNull: true;
|
|
235
|
+
hasDefault: false;
|
|
236
|
+
isPrimaryKey: false;
|
|
237
|
+
isAutoincrement: false;
|
|
238
|
+
hasRuntimeDefault: false;
|
|
239
|
+
enumValues: undefined;
|
|
240
|
+
identity: undefined;
|
|
241
|
+
generated: undefined;
|
|
242
|
+
}>;
|
|
243
|
+
createdAt: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
244
|
+
name: string;
|
|
245
|
+
tableName: "users";
|
|
246
|
+
dataType: "object date";
|
|
247
|
+
data: Date;
|
|
248
|
+
driverParam: string;
|
|
249
|
+
notNull: true;
|
|
250
|
+
hasDefault: false;
|
|
251
|
+
isPrimaryKey: false;
|
|
252
|
+
isAutoincrement: false;
|
|
253
|
+
hasRuntimeDefault: false;
|
|
254
|
+
enumValues: undefined;
|
|
255
|
+
identity: undefined;
|
|
256
|
+
generated: undefined;
|
|
257
|
+
}>;
|
|
258
|
+
updatedAt: import("drizzle-orm/pg-core").PgBuildColumn<"users", import("drizzle-orm/pg-core").SetNotNull<import("drizzle-orm/pg-core").PgTimestampBuilder>, {
|
|
259
|
+
name: string;
|
|
260
|
+
tableName: "users";
|
|
261
|
+
dataType: "object date";
|
|
262
|
+
data: Date;
|
|
263
|
+
driverParam: string;
|
|
264
|
+
notNull: true;
|
|
265
|
+
hasDefault: false;
|
|
266
|
+
isPrimaryKey: false;
|
|
267
|
+
isAutoincrement: false;
|
|
268
|
+
hasRuntimeDefault: false;
|
|
269
|
+
enumValues: undefined;
|
|
270
|
+
identity: undefined;
|
|
271
|
+
generated: undefined;
|
|
272
|
+
}>;
|
|
273
|
+
};
|
|
274
|
+
dialect: "pg";
|
|
275
|
+
}>;
|
|
276
|
+
//# sourceMappingURL=users.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/tables/users.ts"],"names":[],"mappings":"AAUA,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAEvD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwD7C"}
|
package/docs/guide/migrations.md
CHANGED
|
@@ -69,13 +69,14 @@ CREATE TABLE "users" (
|
|
|
69
69
|
"second_factor_secret" text,
|
|
70
70
|
"second_factor_confirmed_at" timestamp(3) with time zone,
|
|
71
71
|
"second_factor_last_step" integer,
|
|
72
|
+
"second_factor_recovery_codes" text[],
|
|
72
73
|
"email_verified_at" timestamp(3) with time zone,
|
|
73
74
|
"version" integer NOT NULL,
|
|
74
75
|
"created_at" timestamp(3) with time zone NOT NULL,
|
|
75
76
|
"updated_at" timestamp(3) with time zone NOT NULL,
|
|
76
77
|
CONSTRAINT "users_id_type_unique" UNIQUE("id","type"),
|
|
77
78
|
CONSTRAINT "users_password_whole" CHECK (("password_hash" is null) = ("password_updated_at" is null)),
|
|
78
|
-
CONSTRAINT "users_second_factor_whole" CHECK (("second_factor_method" is null) = ("second_factor_secret" is null) and ("second_factor_method" is not null or ("second_factor_confirmed_at" is null and "second_factor_last_step" is null))),
|
|
79
|
+
CONSTRAINT "users_second_factor_whole" CHECK (("second_factor_method" is null) = ("second_factor_secret" is null) and ("second_factor_method" is not null or ("second_factor_confirmed_at" is null and "second_factor_last_step" is null and "second_factor_recovery_codes" is null))),
|
|
79
80
|
CONSTRAINT "users_second_factor_values" CHECK ("second_factor_method" in ('totp') and "second_factor_last_step" >= 0)
|
|
80
81
|
);
|
|
81
82
|
CREATE TABLE "logins" (
|
|
@@ -131,14 +132,19 @@ migration creates is what the conformance suites ran on.
|
|
|
131
132
|
returned verbatim and in order, and as rows in `logins`, whose primary
|
|
132
133
|
key is the per-type uniqueness. PostgreSQL has no unique index over the
|
|
133
134
|
elements of an array.
|
|
134
|
-
- **A second factor in
|
|
135
|
-
or neither; a confirmation date
|
|
136
|
-
the only method, and a last step never negative. The
|
|
135
|
+
- **A second factor in five columns, checked whole.** A method and a secret,
|
|
136
|
+
or neither; a confirmation date, a last step and recovery codes only beside
|
|
137
|
+
them; `totp` the only method, and a last step never negative. The
|
|
137
138
|
secret is opaque to the store and kept byte for byte: `@nxgt/janus` seals
|
|
138
139
|
it before the store sees it, so a dump of `users` cannot produce a code.
|
|
139
140
|
Its key id is the second part of the value, so
|
|
140
141
|
`where second_factor_secret like 'v1.<id>.%'` finds the secrets still sealed
|
|
141
142
|
with a key being [rotated out](https://github.com/softistx/nxgt-janus/blob/develop/packages/janus/docs/guide/second-factor.md#rotating-the-keys).
|
|
143
|
+
- **Recovery codes as a `text[]`, `null` read as `[]`.** Each element is a
|
|
144
|
+
keyed hash the core wrote, never a code, kept in order. No codes — a
|
|
145
|
+
factor with none left, or no factor at all — is `null`, never `{}`. A row
|
|
146
|
+
from before the column existed reads as a factor with no codes, so an
|
|
147
|
+
upgrade rewrites nothing.
|
|
142
148
|
- **`attempts` is `not null default 0`**, checked `>= 0`, so the rows an upgrade finds read
|
|
143
149
|
as tokens with no attempt counted yet.
|
|
144
150
|
- **No foreign key from sessions and tokens to users.** Deleting a user
|
|
@@ -182,6 +188,31 @@ Those are the statements drizzle-kit writes from the 0.1 tables to the 0.2
|
|
|
182
188
|
ones, in its own order and separated by `--> statement-breakpoint`; with a
|
|
183
189
|
PostgreSQL schema of its own, every name is qualified.
|
|
184
190
|
|
|
191
|
+
### To 0.4: recovery codes
|
|
192
|
+
|
|
193
|
+
**A migration is required.** One nullable column, and the whole-factor check
|
|
194
|
+
extended to it, so the migration rewrites no row and every existing factor
|
|
195
|
+
reads as having no recovery codes until its user is given some:
|
|
196
|
+
|
|
197
|
+
```sql
|
|
198
|
+
ALTER TABLE "users" ADD COLUMN "second_factor_recovery_codes" text[];
|
|
199
|
+
ALTER TABLE "users" DROP CONSTRAINT "users_second_factor_whole", ADD CONSTRAINT "users_second_factor_whole" CHECK (("second_factor_method" is null) = ("second_factor_secret" is null) and ("second_factor_method" is not null or ("second_factor_confirmed_at" is null and "second_factor_last_step" is null and "second_factor_recovery_codes" is null)));
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Those are the statements drizzle-kit writes from the 0.3 tables to the 0.4
|
|
203
|
+
ones. Re-adding the check validates every row, under a lock on `users` for
|
|
204
|
+
the length of one scan; the rows already satisfy it, since the column is new.
|
|
205
|
+
Deployed before the migration, every query on `users` fails with
|
|
206
|
+
`STORE_FAILED`, caused by `column "second_factor_recovery_codes" does not
|
|
207
|
+
exist`.
|
|
208
|
+
|
|
209
|
+
**Finish the rollout before users hold codes.** An instance still on 0.3
|
|
210
|
+
knows four columns: once a user holds recovery codes, its `disable` leaves
|
|
211
|
+
them beside a factor removed, and the check refuses the write with
|
|
212
|
+
`STORE_FAILED`. Nothing is lost — the call fails, and succeeds on an instance
|
|
213
|
+
of 0.4 — but run 0.3 and 0.4 side by side only while no user has been given
|
|
214
|
+
codes, which `@nxgt/janus` does from `secondFactor.activate` on.
|
|
215
|
+
|
|
185
216
|
## Collecting lapsed sessions
|
|
186
217
|
|
|
187
218
|
PostgreSQL has no TTL. A lapsed session is refused on every read, but it stays
|
package/docs/roadmap.md
CHANGED
|
@@ -36,6 +36,12 @@ Nothing yet.
|
|
|
36
36
|
|
|
37
37
|
## Shipped
|
|
38
38
|
|
|
39
|
+
- **Recovery codes on a second factor, v0.4.0** — for `@nxgt/janus` 0.10:
|
|
40
|
+
`users` gains `second_factor_recovery_codes`, a nullable `text[]` of the
|
|
41
|
+
codes' keyed hashes, read in order and `null` as `[]`, and the
|
|
42
|
+
`users_second_factor_whole` check keeps them to a row with a factor. A
|
|
43
|
+
migration, generated by drizzle-kit, adds the column without rewriting a
|
|
44
|
+
row.
|
|
39
45
|
- **A user's tokens spent in one statement, v0.3.0** — for `@nxgt/janus`
|
|
40
46
|
0.7: `spendUserTokens(userId, kind, at, except?)` spends the unspent
|
|
41
47
|
tokens of one user and kind — but `except` — in one `update`, and passes
|
package/docs/troubleshooting.md
CHANGED
|
@@ -33,6 +33,7 @@ How PostgreSQL's errors become the port's:
|
|
|
33
33
|
**Setup**
|
|
34
34
|
- [`STORE_FAILED` caused by `relation "users" does not exist`](#store_failed-caused-by-relation-users-does-not-exist)
|
|
35
35
|
- [`STORE_FAILED` caused by `column "second_factor_method" does not exist`](#store_failed-caused-by-column-second_factor_method-does-not-exist)
|
|
36
|
+
- [`STORE_FAILED` caused by `column "second_factor_recovery_codes" does not exist`](#store_failed-caused-by-column-second_factor_recovery_codes-does-not-exist)
|
|
36
37
|
- [`STORE_FAILED` caused by `column "type" does not exist`](#store_failed-caused-by-column-type-does-not-exist)
|
|
37
38
|
- [`schema "janus" does not exist` while migrating](#schema-janus-does-not-exist-while-migrating)
|
|
38
39
|
- [`syntax error at or near "NULLS"` while migrating](#syntax-error-at-or-near-nulls-while-migrating)
|
|
@@ -204,6 +205,26 @@ bunx drizzle-kit generate --config drizzle.janus.config.ts
|
|
|
204
205
|
bunx drizzle-kit migrate --config drizzle.janus.config.ts
|
|
205
206
|
```
|
|
206
207
|
|
|
208
|
+
### `STORE_FAILED` caused by `column "second_factor_recovery_codes" does not exist`
|
|
209
|
+
|
|
210
|
+
The code is `42703`.
|
|
211
|
+
|
|
212
|
+
**When:** after upgrading to 0.4, on the first query that reads or writes a
|
|
213
|
+
user — a sign-up, a sign-in, `authenticate`.
|
|
214
|
+
|
|
215
|
+
**Why:** `users` gained the `second_factor_recovery_codes` column, which holds
|
|
216
|
+
the recovery codes' hashes, and the migration that adds it was not generated,
|
|
217
|
+
or not applied.
|
|
218
|
+
|
|
219
|
+
**Fix:** generate and apply it before the new version serves requests. It adds
|
|
220
|
+
one nullable column and re-creates the `users_second_factor_whole` check; see
|
|
221
|
+
[To 0.4](guide/migrations.md#to-04-recovery-codes).
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
bunx drizzle-kit generate --config drizzle.janus.config.ts
|
|
225
|
+
bunx drizzle-kit migrate --config drizzle.janus.config.ts
|
|
226
|
+
```
|
|
227
|
+
|
|
207
228
|
### `STORE_FAILED` caused by `column "type" does not exist`
|
|
208
229
|
|
|
209
230
|
Also `column "type" of relation "users" does not exist`, or another column of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/janus-drizzle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The PostgreSQL adapter for @nxgt/janus on Drizzle and @nxgt/drizzle: users, sessions, one-time tokens and permission tuples",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@electric-sql/pglite": "0.5.8",
|
|
53
53
|
"@nxgt/drizzle": "0.6.1",
|
|
54
|
-
"@nxgt/janus": "^0.
|
|
54
|
+
"@nxgt/janus": "^0.10.0",
|
|
55
55
|
"@types/bun": "^1.4.2",
|
|
56
56
|
"@types/pg": "^8.23.1",
|
|
57
57
|
"drizzle-kit": "1.0.0-rc.4",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"@nxgt/drizzle": ">=0.6.1 <1",
|
|
65
|
-
"@nxgt/janus": "^0.
|
|
65
|
+
"@nxgt/janus": "^0.10.0",
|
|
66
66
|
"drizzle-orm": ">=1.0.0-rc.4 <2",
|
|
67
67
|
"typescript": "^6.0.3"
|
|
68
68
|
}
|
package/dist/relations.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"relations.d.ts","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,kBAAkB,CAAC;AAQpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,EACN,KAAK,qBAAqB,EAG1B,MAAM,UAAU,CAAC;AAGlB;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACrC,EAAE,EAAE,UAAU,EACd,OAAO,GAAE,qBAAqB,CAAC,WAAW,CAAM,GAC9C,aAAa,CA2Hf"}
|
package/dist/stores.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stores.d.ts","sourceRoot":"","sources":["../src/stores.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAEX,WAAW,EAOX,MAAM,aAAa,CAAC;AAGrB,OAAO,EACN,KAAK,qBAAqB,EAG1B,MAAM,UAAU,CAAC;AAOlB,iDAAiD;AACjD,KAAK,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;AAGhE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAClC,EAAE,EAAE,UAAU,EACd,OAAO,GAAE,qBAAqB,CAAC,aAAa,CAAM,GAChD,WAAW,CAOb"}
|
package/dist/tables.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../src/tables.ts"],"names":[],"mappings":"AACA,OAAO,EAQN,KAAK,QAAQ,EAMb,MAAM,qBAAqB,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,6BAA6B;AAC7B,MAAM,WAAW,kBAAkB;IAClC;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAiBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+KjE;AAED,mDAAmD;AACnD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,qBAAqB,CACrC,CAAC,SAAS,MAAM,WAAW,GAAG,MAAM,WAAW;IAE/C,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;CACvC"}
|