@oidcraft/typeorm 0.1.0 → 0.2.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/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { DataSource, EntitySchema, QueryRunner } from "typeorm";
2
1
  import { AccountStore, FullAdapter, KeyStore } from "@oidcraft/adapter";
2
+ import { DataSource, EntitySchema, QueryRunner } from "typeorm";
3
3
  type Migration = {
4
4
  id: string;
5
5
  statements: string[];
@@ -17,24 +17,29 @@ export declare const POSTGRES_MIGRATIONS: Migration[];
17
17
  * deploy, from several processes at once (ARCHITECTURE.md §4.2, FR-A4).
18
18
  */
19
19
  export declare const SQLITE_MIGRATIONS: Migration[];
20
- export type ClientRow = {
20
+ /**
21
+ * The rows of the shared schema as Kysely and TypeORM read and write them: JSON as text, times as
22
+ * epoch milliseconds. Drizzle declares its columns as JSON and timestamps and maps them itself.
23
+ */
24
+ type ClientRow = {
21
25
  client_id: string;
22
26
  data: string;
23
27
  created_at: number;
24
28
  updated_at: number;
25
29
  };
26
- export type ArtifactRow = {
30
+ type ArtifactRow = {
27
31
  kind: string;
28
32
  id: string;
29
33
  client_id: string;
30
34
  account_id: string | null;
31
35
  grant_id: string | null;
36
+ family_id: string | null;
32
37
  user_code: string | null;
33
38
  payload: string;
34
39
  consumed_at: number | null;
35
40
  expires_at: number;
36
41
  };
37
- export type SessionRow = {
42
+ type SessionRow = {
38
43
  id: string;
39
44
  account_id: string;
40
45
  auth_time: number;
@@ -45,7 +50,7 @@ export type SessionRow = {
45
50
  clients: string;
46
51
  expires_at: number;
47
52
  };
48
- export type GrantRow = {
53
+ type GrantRow = {
49
54
  id: string;
50
55
  account_id: string;
51
56
  client_id: string;
@@ -55,7 +60,7 @@ export type GrantRow = {
55
60
  created_at: number;
56
61
  expires_at: number | null;
57
62
  };
58
- export type IdentityRow = {
63
+ type IdentityRow = {
59
64
  provider: string;
60
65
  subject: string;
61
66
  account_id: string;
@@ -63,7 +68,7 @@ export type IdentityRow = {
63
68
  claims: string;
64
69
  linked_at: number;
65
70
  };
66
- export type RealmRow = {
71
+ type RealmRow = {
67
72
  id: string;
68
73
  display_name: string | null;
69
74
  enabled: number;
@@ -71,12 +76,12 @@ export type RealmRow = {
71
76
  created_at: number;
72
77
  updated_at: number;
73
78
  };
74
- export type ReplayRow = {
79
+ type ReplayRow = {
75
80
  namespace: string;
76
81
  value: string;
77
82
  expires_at: number;
78
83
  };
79
- export type CredentialRow = {
84
+ type CredentialRow = {
80
85
  account_id: string;
81
86
  username: string;
82
87
  password_hash: string;
@@ -127,4 +132,4 @@ export type TypeormAdapterOptions = {
127
132
  * The queries are dialect-agnostic; the schema shipped and tested is SQLite's, as for the others.
128
133
  */
129
134
  export declare const typeormAdapter: (options: TypeormAdapterOptions) => FullAdapter;
130
- export type { Migration };
135
+ export type { ArtifactRow, ClientRow, CredentialRow, GrantRow, IdentityRow, Migration, RealmRow, ReplayRow, SessionRow };
package/dist/index.mjs CHANGED
@@ -1,4 +1,147 @@
1
+ import { noAccounts } from "@oidcraft/adapter";
1
2
  import { EntitySchema, QueryFailedError } from "typeorm";
3
+ // ../sql/dist/rows.mjs
4
+ /** Never `new Date` on the raw value: a string there is read as a date, not as milliseconds. */
5
+ const at = (millis) => new Date(Number(millis));
6
+ const json = (value) => JSON.stringify(value);
7
+ const parse = (value) => JSON.parse(value);
8
+ const clientRow = (client) => ({
9
+ client_id: client.clientId,
10
+ data: json(client),
11
+ created_at: client.createdAt.getTime(),
12
+ updated_at: client.updatedAt.getTime()
13
+ });
14
+ const toClient = (row) => ({
15
+ ...parse(row.data),
16
+ createdAt: at(row.created_at),
17
+ updatedAt: at(row.updated_at)
18
+ });
19
+ /** `user_code` is lifted out of the payload so a device's code is found by index (FR-C7). */
20
+ const artifactRow = (artifact) => ({
21
+ kind: artifact.kind,
22
+ id: artifact.id,
23
+ client_id: artifact.clientId,
24
+ account_id: artifact.accountId ?? null,
25
+ grant_id: artifact.grantId ?? null,
26
+ family_id: artifact.familyId ?? null,
27
+ user_code: artifact.payload.userCode ?? null,
28
+ payload: json(artifact.payload),
29
+ consumed_at: artifact.consumedAt ? artifact.consumedAt.getTime() : null,
30
+ expires_at: artifact.expiresAt.getTime()
31
+ });
32
+ const toArtifact = (row) => ({
33
+ id: row.id,
34
+ kind: row.kind,
35
+ clientId: row.client_id,
36
+ ...row.account_id && { accountId: row.account_id },
37
+ ...row.grant_id && { grantId: row.grant_id },
38
+ ...row.family_id && { familyId: row.family_id },
39
+ payload: parse(row.payload),
40
+ ...row.consumed_at && { consumedAt: at(row.consumed_at) },
41
+ expiresAt: at(row.expires_at)
42
+ });
43
+ const sessionRow = (session) => ({
44
+ id: session.id,
45
+ account_id: session.accountId,
46
+ auth_time: session.authTime.getTime(),
47
+ acr: session.acr ?? null,
48
+ amr: session.amr ? json(session.amr) : null,
49
+ idp: session.idp ?? null,
50
+ upstream_session_id: session.upstreamSessionId ?? null,
51
+ clients: json(session.clients),
52
+ expires_at: session.expiresAt.getTime()
53
+ });
54
+ const toSession = (row) => ({
55
+ id: row.id,
56
+ accountId: row.account_id,
57
+ authTime: at(row.auth_time),
58
+ ...row.acr && { acr: row.acr },
59
+ ...row.amr ? { amr: parse(row.amr) } : {},
60
+ ...row.idp && { idp: row.idp },
61
+ ...row.upstream_session_id && { upstreamSessionId: row.upstream_session_id },
62
+ clients: parse(row.clients),
63
+ expiresAt: at(row.expires_at)
64
+ });
65
+ const grantRow = (grant) => ({
66
+ id: grant.id,
67
+ account_id: grant.accountId,
68
+ client_id: grant.clientId,
69
+ scopes: json(grant.scopes),
70
+ claims: json(grant.claims),
71
+ resources: json(grant.resources),
72
+ created_at: grant.createdAt.getTime(),
73
+ expires_at: grant.expiresAt ? grant.expiresAt.getTime() : null
74
+ });
75
+ const toGrant = (row) => ({
76
+ id: row.id,
77
+ accountId: row.account_id,
78
+ clientId: row.client_id,
79
+ scopes: parse(row.scopes),
80
+ claims: parse(row.claims),
81
+ resources: parse(row.resources),
82
+ createdAt: at(row.created_at),
83
+ ...row.expires_at && { expiresAt: at(row.expires_at) }
84
+ });
85
+ const identityRow = (identity) => ({
86
+ provider: identity.provider,
87
+ subject: identity.subject,
88
+ account_id: identity.accountId,
89
+ email: identity.email ?? null,
90
+ claims: json(identity.claims),
91
+ linked_at: identity.linkedAt.getTime()
92
+ });
93
+ const toIdentity = (row) => ({
94
+ accountId: row.account_id,
95
+ provider: row.provider,
96
+ subject: row.subject,
97
+ ...row.email && { email: row.email },
98
+ claims: parse(row.claims),
99
+ linkedAt: at(row.linked_at)
100
+ });
101
+ const credentialRow = (credential) => ({
102
+ account_id: credential.accountId,
103
+ username: credential.username,
104
+ password_hash: credential.passwordHash,
105
+ claims: json(credential.claims),
106
+ totp_secret: credential.totp?.secret ?? null,
107
+ totp_confirmed_at: credential.totp?.confirmedAt?.getTime() ?? null,
108
+ recovery_codes: credential.recoveryCodes ? json(credential.recoveryCodes) : null,
109
+ failed_attempts: credential.failedAttempts,
110
+ locked_until: credential.lockedUntil?.getTime() ?? null,
111
+ created_at: credential.createdAt.getTime(),
112
+ updated_at: credential.updatedAt.getTime()
113
+ });
114
+ const toCredential = (row) => ({
115
+ accountId: row.account_id,
116
+ username: row.username,
117
+ passwordHash: row.password_hash,
118
+ claims: parse(row.claims),
119
+ ...row.totp_secret && { totp: {
120
+ secret: row.totp_secret,
121
+ ...row.totp_confirmed_at && { confirmedAt: at(row.totp_confirmed_at) }
122
+ } },
123
+ ...row.recovery_codes ? { recoveryCodes: parse(row.recovery_codes) } : {},
124
+ failedAttempts: Number(row.failed_attempts),
125
+ ...row.locked_until && { lockedUntil: at(row.locked_until) },
126
+ createdAt: at(row.created_at),
127
+ updatedAt: at(row.updated_at)
128
+ });
129
+ const realmRow = (realm) => ({
130
+ id: realm.id,
131
+ display_name: realm.displayName ?? null,
132
+ enabled: realm.enabled ? 1 : 0,
133
+ settings: json(realm.settings),
134
+ created_at: realm.createdAt.getTime(),
135
+ updated_at: realm.updatedAt.getTime()
136
+ });
137
+ const toRealm = (row) => ({
138
+ id: row.id,
139
+ ...row.display_name && { displayName: row.display_name },
140
+ enabled: Boolean(Number(row.enabled)),
141
+ settings: parse(row.settings),
142
+ createdAt: at(row.created_at),
143
+ updatedAt: at(row.updated_at)
144
+ });
2
145
  // ../sql/dist/postgres.mjs
3
146
  /**
4
147
  * The same tables as `SQLITE_MIGRATIONS`, with the same ids, for Postgres (FR-A4). Milliseconds are
@@ -36,6 +179,18 @@ const POSTGRES_MIGRATIONS = [
36
179
  {
37
180
  id: "0003_realms",
38
181
  statements: ["create table oidc_realms (id text primary key, display_name text, enabled integer not null, settings text not null, created_at bigint not null, updated_at bigint not null)"]
182
+ },
183
+ {
184
+ id: "0004_indexes",
185
+ statements: [
186
+ "create index oidc_artifacts_client_idx on oidc_artifacts (client_id)",
187
+ "create index oidc_grants_client_idx on oidc_grants (client_id)",
188
+ "create index oidc_sessions_expiry_idx on oidc_sessions (expires_at)"
189
+ ]
190
+ },
191
+ {
192
+ id: "0005_families",
193
+ statements: ["alter table oidc_artifacts add column family_id text", "create index oidc_artifacts_family_idx on oidc_artifacts (family_id)"]
39
194
  }
40
195
  ];
41
196
  // ../sql/dist/sqlite.mjs
@@ -74,6 +229,18 @@ const SQLITE_MIGRATIONS = [
74
229
  {
75
230
  id: "0003_realms",
76
231
  statements: ["create table oidc_realms (id text primary key, display_name text, enabled integer not null, settings text not null, created_at integer not null, updated_at integer not null)"]
232
+ },
233
+ {
234
+ id: "0004_indexes",
235
+ statements: [
236
+ "create index oidc_artifacts_client_idx on oidc_artifacts (client_id)",
237
+ "create index oidc_grants_client_idx on oidc_grants (client_id)",
238
+ "create index oidc_sessions_expiry_idx on oidc_sessions (expires_at)"
239
+ ]
240
+ },
241
+ {
242
+ id: "0005_families",
243
+ statements: ["alter table oidc_artifacts add column family_id text", "create index oidc_artifacts_family_idx on oidc_artifacts (family_id)"]
77
244
  }
78
245
  ];
79
246
  // src/schema.ts
@@ -109,6 +276,7 @@ const ArtifactEntity = new EntitySchema({
109
276
  client_id: text(),
110
277
  account_id: text({ nullable: true }),
111
278
  grant_id: text({ nullable: true }),
279
+ family_id: text({ nullable: true }),
112
280
  user_code: text({ nullable: true }),
113
281
  payload: text(),
114
282
  consumed_at: millis({ nullable: true }),
@@ -126,6 +294,14 @@ const ArtifactEntity = new EntitySchema({
126
294
  {
127
295
  name: "oidc_artifacts_expiry_idx",
128
296
  columns: ["expires_at"]
297
+ },
298
+ {
299
+ name: "oidc_artifacts_client_idx",
300
+ columns: ["client_id"]
301
+ },
302
+ {
303
+ name: "oidc_artifacts_family_idx",
304
+ columns: ["family_id"]
129
305
  }
130
306
  ]
131
307
  });
@@ -143,13 +319,20 @@ const SessionEntity = new EntitySchema({
143
319
  clients: text(),
144
320
  expires_at: millis()
145
321
  },
146
- indices: [{
147
- name: "oidc_sessions_account_idx",
148
- columns: ["account_id"]
149
- }, {
150
- name: "oidc_sessions_upstream_idx",
151
- columns: ["idp", "upstream_session_id"]
152
- }]
322
+ indices: [
323
+ {
324
+ name: "oidc_sessions_account_idx",
325
+ columns: ["account_id"]
326
+ },
327
+ {
328
+ name: "oidc_sessions_upstream_idx",
329
+ columns: ["idp", "upstream_session_id"]
330
+ },
331
+ {
332
+ name: "oidc_sessions_expiry_idx",
333
+ columns: ["expires_at"]
334
+ }
335
+ ]
153
336
  });
154
337
  const GrantEntity = new EntitySchema({
155
338
  name: "oidc_grants",
@@ -168,6 +351,9 @@ const GrantEntity = new EntitySchema({
168
351
  name: "oidc_grants_account_client_idx",
169
352
  columns: ["account_id", "client_id"],
170
353
  unique: true
354
+ }, {
355
+ name: "oidc_grants_client_idx",
356
+ columns: ["client_id"]
171
357
  }]
172
358
  });
173
359
  const IdentityEntity = new EntitySchema({
@@ -271,95 +457,6 @@ const TYPEORM_SQLITE_MIGRATIONS = SQLITE_MIGRATIONS.map(asTypeormMigration);
271
457
  const TYPEORM_POSTGRES_MIGRATIONS = POSTGRES_MIGRATIONS.map(asTypeormMigration);
272
458
  // src/index.ts
273
459
  const now = () => Date.now();
274
- const json = (value) => JSON.stringify(value);
275
- const parse = (value) => JSON.parse(value);
276
- const toArtifact = (row) => ({
277
- id: row.id,
278
- kind: row.kind,
279
- clientId: row.client_id,
280
- ...row.account_id && { accountId: row.account_id },
281
- ...row.grant_id && { grantId: row.grant_id },
282
- payload: parse(row.payload),
283
- ...row.consumed_at && { consumedAt: new Date(row.consumed_at) },
284
- expiresAt: new Date(row.expires_at)
285
- });
286
- const toSession = (row) => ({
287
- id: row.id,
288
- accountId: row.account_id,
289
- authTime: new Date(row.auth_time),
290
- ...row.acr && { acr: row.acr },
291
- ...row.amr ? { amr: parse(row.amr) } : {},
292
- ...row.idp && { idp: row.idp },
293
- ...row.upstream_session_id && { upstreamSessionId: row.upstream_session_id },
294
- clients: parse(row.clients),
295
- expiresAt: new Date(row.expires_at)
296
- });
297
- const toGrant = (row) => ({
298
- id: row.id,
299
- accountId: row.account_id,
300
- clientId: row.client_id,
301
- scopes: parse(row.scopes),
302
- claims: parse(row.claims),
303
- resources: parse(row.resources),
304
- createdAt: new Date(row.created_at),
305
- ...row.expires_at && { expiresAt: new Date(row.expires_at) }
306
- });
307
- const credentialRow = (credential) => ({
308
- account_id: credential.accountId,
309
- username: credential.username,
310
- password_hash: credential.passwordHash,
311
- claims: json(credential.claims),
312
- totp_secret: credential.totp?.secret ?? null,
313
- totp_confirmed_at: credential.totp?.confirmedAt?.getTime() ?? null,
314
- recovery_codes: credential.recoveryCodes ? json(credential.recoveryCodes) : null,
315
- failed_attempts: credential.failedAttempts,
316
- locked_until: credential.lockedUntil?.getTime() ?? null,
317
- created_at: credential.createdAt.getTime(),
318
- updated_at: credential.updatedAt.getTime()
319
- });
320
- const toCredential = (row) => ({
321
- accountId: row.account_id,
322
- username: row.username,
323
- passwordHash: row.password_hash,
324
- claims: parse(row.claims),
325
- ...row.totp_secret && { totp: {
326
- secret: row.totp_secret,
327
- ...row.totp_confirmed_at && { confirmedAt: new Date(row.totp_confirmed_at) }
328
- } },
329
- ...row.recovery_codes ? { recoveryCodes: parse(row.recovery_codes) } : {},
330
- failedAttempts: row.failed_attempts,
331
- ...row.locked_until && { lockedUntil: new Date(row.locked_until) },
332
- createdAt: new Date(row.created_at),
333
- updatedAt: new Date(row.updated_at)
334
- });
335
- const toIdentity = (row) => ({
336
- accountId: row.account_id,
337
- provider: row.provider,
338
- subject: row.subject,
339
- ...row.email && { email: row.email },
340
- claims: parse(row.claims),
341
- linkedAt: new Date(row.linked_at)
342
- });
343
- const realmRow = (realm) => ({
344
- id: realm.id,
345
- display_name: realm.displayName ?? null,
346
- enabled: realm.enabled ? 1 : 0,
347
- settings: json(realm.settings),
348
- created_at: realm.createdAt.getTime(),
349
- updated_at: realm.updatedAt.getTime()
350
- });
351
- const toRealm = (row) => ({
352
- id: row.id,
353
- ...row.display_name && { displayName: row.display_name },
354
- enabled: Boolean(row.enabled),
355
- settings: parse(row.settings),
356
- createdAt: new Date(row.created_at),
357
- updatedAt: new Date(row.updated_at)
358
- });
359
- const noAccounts = {
360
- async find() {},
361
- async claims() {}
362
- };
363
460
  const upsert = async (repository, row, conflict, overwrite) => {
364
461
  const columns = overwrite ?? Object.keys(row).filter((column) => !conflict.includes(column));
365
462
  await repository.createQueryBuilder().insert().values(row).orUpdate(columns, conflict).execute();
@@ -396,21 +493,12 @@ const typeormAdapter = (options) => {
396
493
  const realms = dataSource.getRepository(RealmEntity);
397
494
  const artifacts = {
398
495
  async upsert(artifact) {
399
- const row = {
400
- kind: artifact.kind,
401
- id: artifact.id,
402
- client_id: artifact.clientId,
403
- account_id: artifact.accountId ?? null,
404
- grant_id: artifact.grantId ?? null,
405
- user_code: artifact.payload.userCode ?? null,
406
- payload: json(artifact.payload),
407
- consumed_at: artifact.consumedAt ? artifact.consumedAt.getTime() : null,
408
- expires_at: artifact.expiresAt.getTime()
409
- };
496
+ const row = artifactRow(artifact);
410
497
  await upsert(artifactRows, row, ["kind", "id"], [
411
498
  "client_id",
412
499
  "account_id",
413
500
  "grant_id",
501
+ "family_id",
414
502
  "user_code",
415
503
  "payload",
416
504
  "expires_at"
@@ -447,6 +535,12 @@ const typeormAdapter = (options) => {
447
535
  async revokeByGrantId(grantId) {
448
536
  await artifactRows.delete({ grant_id: grantId });
449
537
  },
538
+ async revokeByFamilyId(familyId) {
539
+ await artifactRows.delete({ family_id: familyId });
540
+ },
541
+ async revokeByClientId(clientId) {
542
+ await artifactRows.delete({ client_id: clientId });
543
+ },
450
544
  async prune(before, limit) {
451
545
  const doomed = await artifactRows.createQueryBuilder("a").select(["a.kind", "a.id"]).where("a.expires_at <= :before", { before: before.getTime() }).limit(limit).getMany();
452
546
  for (const row of doomed) await artifacts.destroy(row.kind, row.id);
@@ -458,30 +552,25 @@ const typeormAdapter = (options) => {
458
552
  async find(clientId) {
459
553
  const row = await clients.findOneBy({ client_id: clientId });
460
554
  if (!row) return void 0;
461
- return {
462
- ...parse(row.data),
463
- createdAt: new Date(row.created_at),
464
- updatedAt: new Date(row.updated_at)
465
- };
555
+ return toClient(row);
466
556
  },
467
557
  async upsert(client) {
468
- const row = {
469
- client_id: client.clientId,
470
- data: json(client),
471
- created_at: client.createdAt.getTime(),
472
- updated_at: client.updatedAt.getTime()
473
- };
474
- await upsert(clients, row, ["client_id"], ["data", "updated_at"]);
558
+ await upsert(clients, clientRow(client), ["client_id"], ["data", "updated_at"]);
475
559
  },
476
560
  async destroy(clientId) {
477
561
  await clients.delete({ client_id: clientId });
478
562
  },
479
- async list() {
480
- return { clients: (await clients.find()).map((row) => ({
481
- ...parse(row.data),
482
- createdAt: new Date(row.created_at),
483
- updatedAt: new Date(row.updated_at)
484
- })) };
563
+ async list(cursor, limit) {
564
+ const query = clients.createQueryBuilder("c").orderBy("c.client_id");
565
+ if (cursor !== void 0) query.where("c.client_id > :cursor", { cursor });
566
+ if (limit !== void 0) query.limit(limit + 1);
567
+ const rows = await query.getMany();
568
+ const more = limit !== void 0 && rows.length > limit;
569
+ const shown = more ? rows.slice(0, limit) : rows;
570
+ return {
571
+ clients: shown.map(toClient),
572
+ ...more && { cursor: shown.at(-1)?.client_id }
573
+ };
485
574
  }
486
575
  },
487
576
  artifacts,
@@ -500,24 +589,14 @@ const typeormAdapter = (options) => {
500
589
  }).getMany()).map(toSession);
501
590
  },
502
591
  async findByUpstreamSession(provider, upstreamSessionId) {
503
- return (await sessions.findBy({
504
- idp: provider,
505
- upstream_session_id: upstreamSessionId
506
- })).map(toSession);
592
+ return (await sessions.createQueryBuilder("s").where("s.idp = :provider and s.upstream_session_id = :upstreamSessionId and s.expires_at > :now", {
593
+ provider,
594
+ upstreamSessionId,
595
+ now: now()
596
+ }).getMany()).map(toSession);
507
597
  },
508
598
  async upsert(session) {
509
- const row = {
510
- id: session.id,
511
- account_id: session.accountId,
512
- auth_time: session.authTime.getTime(),
513
- acr: session.acr ?? null,
514
- amr: session.amr ? json(session.amr) : null,
515
- idp: session.idp ?? null,
516
- upstream_session_id: session.upstreamSessionId ?? null,
517
- clients: json(session.clients),
518
- expires_at: session.expiresAt.getTime()
519
- };
520
- await upsert(sessions, row, ["id"]);
599
+ await upsert(sessions, sessionRow(session), ["id"]);
521
600
  },
522
601
  async addClient(id, clientId) {
523
602
  for (let attempt = 0; attempt < 64; attempt++) {
@@ -537,6 +616,14 @@ const typeormAdapter = (options) => {
537
616
  },
538
617
  async destroy(id) {
539
618
  await sessions.delete({ id });
619
+ },
620
+ async prune(before, limit) {
621
+ const doomed = await sessions.createQueryBuilder("s").select(["s.id"]).where("s.expires_at <= :before", { before: before.getTime() }).limit(limit).getMany();
622
+ for (const row of doomed) await sessions.createQueryBuilder().delete().where("id = :id and expires_at <= :before", {
623
+ id: row.id,
624
+ before: before.getTime()
625
+ }).execute();
626
+ return doomed.length;
540
627
  }
541
628
  },
542
629
  grants: {
@@ -555,20 +642,18 @@ const typeormAdapter = (options) => {
555
642
  return (await grants.findBy({ account_id: accountId })).map(toGrant);
556
643
  },
557
644
  async upsert(grant) {
558
- const row = {
559
- id: grant.id,
560
- account_id: grant.accountId,
561
- client_id: grant.clientId,
562
- scopes: json(grant.scopes),
563
- claims: json(grant.claims),
564
- resources: json(grant.resources),
565
- created_at: grant.createdAt.getTime(),
566
- expires_at: grant.expiresAt ? grant.expiresAt.getTime() : null
567
- };
568
- await upsert(grants, row, ["id"]);
645
+ await upsert(grants, grantRow(grant), ["account_id", "client_id"], [
646
+ "scopes",
647
+ "claims",
648
+ "resources",
649
+ "expires_at"
650
+ ]);
569
651
  },
570
652
  async destroy(id) {
571
653
  await grants.delete({ id });
654
+ },
655
+ async destroyByClientId(clientId) {
656
+ await grants.delete({ client_id: clientId });
572
657
  }
573
658
  },
574
659
  accounts: options.accounts ?? noAccounts,
@@ -610,21 +695,31 @@ const typeormAdapter = (options) => {
610
695
  }
611
696
  },
612
697
  keys: options.keys,
613
- replay: { async claim(namespace, value, ttl) {
614
- await replay.createQueryBuilder().delete().where("namespace = :namespace and value = :value and expires_at <= :now", {
615
- namespace,
616
- value,
617
- now: now()
618
- }).execute();
619
- return insertIfAbsent(replay, {
620
- namespace,
621
- value,
622
- expires_at: now() + ttl * 1e3
623
- }, async () => replay.existsBy({
624
- namespace,
625
- value
626
- }));
627
- } },
698
+ replay: {
699
+ async claim(namespace, value, ttl) {
700
+ await replay.createQueryBuilder().delete().where("namespace = :namespace and value = :value and expires_at <= :now", {
701
+ namespace,
702
+ value,
703
+ now: now()
704
+ }).execute();
705
+ return insertIfAbsent(replay, {
706
+ namespace,
707
+ value,
708
+ expires_at: now() + ttl * 1e3
709
+ }, async () => replay.existsBy({
710
+ namespace,
711
+ value
712
+ }));
713
+ },
714
+ async prune(before, limit) {
715
+ const doomed = await replay.createQueryBuilder("r").select(["r.namespace", "r.value"]).where("r.expires_at <= :before", { before: before.getTime() }).limit(limit).getMany();
716
+ for (const row of doomed) await replay.createQueryBuilder().delete().where("namespace = :namespace and value = :value and expires_at <= :before", {
717
+ ...row,
718
+ before: before.getTime()
719
+ }).execute();
720
+ return doomed.length;
721
+ }
722
+ },
628
723
  identities: {
629
724
  async find(provider, subject) {
630
725
  const row = await identities.findOneBy({
@@ -637,15 +732,7 @@ const typeormAdapter = (options) => {
637
732
  return (await identities.findBy({ account_id: accountId })).map(toIdentity);
638
733
  },
639
734
  async upsert(identity) {
640
- const row = {
641
- provider: identity.provider,
642
- subject: identity.subject,
643
- account_id: identity.accountId,
644
- email: identity.email ?? null,
645
- claims: json(identity.claims),
646
- linked_at: identity.linkedAt.getTime()
647
- };
648
- await upsert(identities, row, ["provider", "subject"]);
735
+ await upsert(identities, identityRow(identity), ["provider", "subject"]);
649
736
  },
650
737
  async destroy(provider, subject) {
651
738
  await identities.delete({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oidcraft/typeorm",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A TypeORM storage adapter for @oidcraft/provider, over a DataSource on Postgres or SQLite.",
5
5
  "license": "MIT",
6
6
  "author": "Fl0r14n <florian.chis@gmail.com>",
@@ -41,11 +41,11 @@
41
41
  "verify:entries": "bun run verify-entries.ts"
42
42
  },
43
43
  "peerDependencies": {
44
- "@oidcraft/adapter": "^0.1.0",
44
+ "@oidcraft/adapter": "^0.2.0",
45
45
  "typeorm": ">=1.1"
46
46
  },
47
47
  "devDependencies": {
48
- "@oidcraft/adapter": "^0.1.0",
48
+ "@oidcraft/adapter": "^0.2.0",
49
49
  "@oidcraft/sql": "0.0.0",
50
50
  "@types/pg": "^8.23.1",
51
51
  "pg": "^8.23.1",