@auth-craft/tenant-access-control-dynamodb 0.0.1 → 0.0.3

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.cjs CHANGED
@@ -4,9 +4,528 @@ var clientDynamodb = require('@aws-sdk/client-dynamodb');
4
4
  var libDynamodb = require('@aws-sdk/lib-dynamodb');
5
5
  var tsMicroResult = require('ts-micro-result');
6
6
  var tenantAccessControl = require('@auth-craft/tenant-access-control');
7
- var databasePluginDynamodb = require('@auth-craft/database-plugin-dynamodb');
8
7
 
9
8
  // src/index.ts
9
+
10
+ // ../auth-core/dist/chunk-66N4CPWZ.mjs
11
+ var USER_STATUS = {
12
+ ACTIVE: "active"};
13
+
14
+ // ../database-plugin-dynamodb/dist/index.mjs
15
+ var EntityPrefix = {
16
+ USER: "USR"
17
+ };
18
+ var PkPrefix = {
19
+ USER: `${EntityPrefix.USER}#`,
20
+ SESSION: "SES#",
21
+ PROVIDER: "PRV#",
22
+ CHALLENGE: "CHL#",
23
+ IDENTITY: "IDT#",
24
+ // Identity (email/phone/username)
25
+ CREDENTIAL: "CRD#",
26
+ // Credential (password, passkey, etc.)
27
+ // Note: MFA credentials use PK: USR#{userId} (stored under user, not separate partition)
28
+ TENANT: "TNT#"
29
+ // TenantMember (SCHEMA_V4 multi-tenant)
30
+ };
31
+ var SkPrefix = {
32
+ METADATA: "MD",
33
+ // Metadata suffix for entities
34
+ AUTH: "AUTH",
35
+ // UserAuth entity (SCHEMA_V4)
36
+ PROFILE: "PROF",
37
+ // UserProfile entity (SCHEMA_V4)
38
+ PROVIDER: "PRV#",
39
+ MFA: "MFA#"
40
+ // UserMFAAuthenticator entity (SCHEMA_V4)
41
+ };
42
+ var DELIMITER = "#";
43
+ var KeyPattern = {
44
+ // ============================================
45
+ // USER
46
+ // ============================================
47
+ /**
48
+ * User PK
49
+ * Pattern: USR#{userId}
50
+ */
51
+ USER_PK: (userId) => `${PkPrefix.USER}${userId}`,
52
+ /**
53
+ * User Auth SK (SCHEMA_V4)
54
+ * Pattern: AUTH
55
+ * Stores: userStatus, mfaRequired, mfaMethods[], createdAt, updatedAt
56
+ *
57
+ * Removed in SCHEMA_V4:
58
+ * - email, phone fields → moved to Identity entity
59
+ * - passwordHash → moved to Credential entity
60
+ * - lockedUntil, failedLoginAttempts → moved to Identity.guards (per-method)
61
+ * - MFA credentials (mfaSecret, mfaBackupCodes) → moved to UserMFAAuthenticator items
62
+ * - Profile fields (givenName, familyName, avatarUrl, locale, metadata) → moved to PROF item
63
+ * - tokenVersion → removed (use Session.revokedAt instead)
64
+ * - permissions → removed from UserAuth (use TenantMember.permMask or Session.permMask)
65
+ */
66
+ USER_AUTH_SK: () => SkPrefix.AUTH,
67
+ /**
68
+ * User Profile SK (SCHEMA_V4)
69
+ * Pattern: PROF
70
+ * Stores: userStatus, deletedAt, givenName, familyName, avatarUrl,
71
+ * locale, metadata, createdAt, updatedAt, statusPk, statusSk
72
+ *
73
+ * Removed in SCHEMA_V4:
74
+ * - email, phone fields → moved to Identity entity
75
+ */
76
+ USER_PROFILE_SK: () => SkPrefix.PROFILE,
77
+ // ============================================
78
+ // USER MFA AUTHENTICATOR (SCHEMA_V4)
79
+ // ============================================
80
+ /**
81
+ * UserMFAAuthenticator SK (single-instance methods like TOTP)
82
+ * Pattern: MFA#{method}
83
+ * Example: MFA#totp
84
+ * Used by: Single-instance MFA methods that don't need ID
85
+ */
86
+ USER_MFA_SK: (method) => `${SkPrefix.MFA}${method}`,
87
+ /**
88
+ * UserMFAAuthenticator SK (multi-instance methods like WebAuthn)
89
+ * Pattern: MFA#{method}#{id}
90
+ * Example: MFA#webauthn#key_abc123, MFA#passkey#pk_xyz789
91
+ * Used by: Multi-instance MFA methods (security keys, passkeys)
92
+ */
93
+ USER_MFA_WITH_ID_SK: (method, id) => `${SkPrefix.MFA}${method}${DELIMITER}${id}`,
94
+ // ============================================
95
+ // TENANT MEMBER (SCHEMA_V4)
96
+ // ============================================
97
+ /**
98
+ * Tenant Member PK
99
+ * Pattern: TNT#{tenantId}
100
+ * Example: TNT#org-abc
101
+ * Used by: DynamoDBTenantMemberItem (main table)
102
+ */
103
+ TENANT_MEMBER_PK: (tenantId) => `${PkPrefix.TENANT}${tenantId}`,
104
+ /**
105
+ * Tenant Member SK
106
+ * Pattern: USR#{userId}
107
+ * Example: USR#01HQZX8V9ABCDEFGHIJK
108
+ * Used by: DynamoDBTenantMemberItem (main table)
109
+ */
110
+ TENANT_MEMBER_SK: (userId) => `${PkPrefix.USER}${userId}`,
111
+ // ============================================
112
+ // SESSION
113
+ // ============================================
114
+ /**
115
+ * Session PK (New session-centric design)
116
+ * Pattern: SES#{sessionId}
117
+ * Example: SES#01HQZX8V9ABCDEFGHIJK
118
+ */
119
+ SESSION_PK: (sessionId) => `${PkPrefix.SESSION}${sessionId}`,
120
+ /**
121
+ * Session Metadata SK
122
+ * Pattern: MD
123
+ * Stores: userId, tokenId, tenantId, audience, permMask, roleIds,
124
+ * deviceLabel, country, city, createdAt, expiresAt, lastUsedAt,
125
+ * revokedAt, metadata, TTL
126
+ * Note: sessionId extracted from PK
127
+ */
128
+ SESSION_METADATA_SK: () => SkPrefix.METADATA,
129
+ /**
130
+ * Session SK (alias for SESSION_METADATA_SK)
131
+ */
132
+ SESSION_SK: () => SkPrefix.METADATA,
133
+ /**
134
+ * Provider SK
135
+ * Pattern: PRV#{provider}#{externalId}
136
+ * Used for:
137
+ * - GSI_UserProviders sort key (userProviderSk)
138
+ * - DynamoDBProviderRegistryItem PK
139
+ */
140
+ PROVIDER_SK: (provider, externalId) => `${SkPrefix.PROVIDER}${provider}${DELIMITER}${externalId}`,
141
+ // ============================================
142
+ // PROVIDER (Provider-centric design)
143
+ // ============================================
144
+ /**
145
+ * Provider PK (Main table)
146
+ * Pattern: PRV#{provider}#{externalId}
147
+ * Example: PRV#google#123456789
148
+ * Used by: DynamoDBUserProviderItem (main table)
149
+ */
150
+ PROVIDER_PK: (provider, externalId) => `${PkPrefix.PROVIDER}${provider}${DELIMITER}${externalId}`,
151
+ /**
152
+ * Provider Metadata SK
153
+ * Pattern: MD
154
+ * Stores: userId, system, externalUsername, externalEmail, externalPhone,
155
+ * externalName, externalAvatarUrl, accessToken, refreshToken,
156
+ * tokenExpiresAt, createdAt, metadata
157
+ * Note: id generated at mapper layer from PK (base64url), NOT stored
158
+ * Note: provider and externalId extracted from PK
159
+ */
160
+ PROVIDER_METADATA_SK: () => SkPrefix.METADATA,
161
+ // ============================================
162
+ // CHALLENGE
163
+ // ============================================
164
+ /**
165
+ * Challenge PK
166
+ * Pattern: CHL#{challengeId}
167
+ * Example: CHL#abc123xyz
168
+ */
169
+ CHALLENGE_PK: (challengeId) => `${PkPrefix.CHALLENGE}${challengeId}`,
170
+ /**
171
+ * Challenge Metadata SK
172
+ * Pattern: MD
173
+ * Stores: userId, tenantId, audience, purpose, status, phase, allowedMethods,
174
+ * method, codeHash, attempts, maxAttempts, metadata, flowKey,
175
+ * expiresAt, createdAt, TTL
176
+ * Note: challengeId extracted from PK
177
+ */
178
+ CHALLENGE_SK: () => SkPrefix.METADATA,
179
+ // ============================================
180
+ // IDENTITY
181
+ // ============================================
182
+ /**
183
+ * Identity PK
184
+ * Pattern: IDT#{type}#{value}
185
+ * Examples:
186
+ * - IDT#email#user@example.com
187
+ * - IDT#phone#+84987654321
188
+ * - IDT#username#johndoe
189
+ *
190
+ * Purpose: Fast O(1) lookup by identity type and value
191
+ */
192
+ IDENTITY_PK: (type, value) => {
193
+ return `${PkPrefix.IDENTITY}${type}#${value}`;
194
+ },
195
+ /**
196
+ * Identity Metadata SK
197
+ * Pattern: MD
198
+ * Stores: userId, loginEnabled, verifiedAt, guards, createdAt, updatedAt
199
+ * Note: type and value extracted from PK
200
+ */
201
+ IDENTITY_SK: () => SkPrefix.METADATA,
202
+ // ============================================
203
+ // CREDENTIAL
204
+ // ============================================
205
+ /**
206
+ * Credential PK
207
+ * Pattern: CRD#{credentialId}
208
+ * Example: CRD#cred_01JCQR8XMZP2KGH7NWV8F9E3TS
209
+ */
210
+ CREDENTIAL_PK: (credentialId) => `${PkPrefix.CREDENTIAL}${credentialId}`,
211
+ /**
212
+ * Credential Metadata SK
213
+ * Pattern: MD
214
+ * Stores: userId, value, metadata, isActive, createdAt, updatedAt, lastUsedAt
215
+ * Note: credentialId and type extracted from PK and GSI SK
216
+ */
217
+ CREDENTIAL_SK: () => SkPrefix.METADATA,
218
+ /**
219
+ * Credential Type Prefix (for GSI query)
220
+ * Pattern: CRD#{type}#
221
+ * Used to query credentials by type with begins_with
222
+ */
223
+ CREDENTIAL_TYPE_PREFIX: (type) => `${PkPrefix.CREDENTIAL}${type}#`,
224
+ /**
225
+ * Credential User GSI SK
226
+ * Pattern: CRD#{type}#{credentialId}
227
+ * Example: CRD#password#cred_01JCQR8XMZP2KGH7NWV8F9E3TS
228
+ * Allows querying credentials by user and filtering by type
229
+ */
230
+ CREDENTIAL_USER_SK: (type, credentialId) => `${PkPrefix.CREDENTIAL}${type}#${credentialId}`,
231
+ // ============================================
232
+ // IDENTITY
233
+ // ============================================
234
+ /**
235
+ * User Index GSI PK (for Identity)
236
+ * Pattern: USR#{userId}
237
+ * Example: USR#01JCQR8XMZP2KGH7NWV8F9E3TS
238
+ *
239
+ * Purpose: Query all identities for a specific user
240
+ */
241
+ USER_INDEX_PK: (userId) => `${PkPrefix.USER}${userId}`,
242
+ /**
243
+ * User Index GSI SK (for Identity)
244
+ * Pattern: IDT#{type}#{value}
245
+ * Examples:
246
+ * - IDT#email#user@example.com
247
+ * - IDT#phone#+84987654321
248
+ *
249
+ * Purpose: Sort identities by type and extract type/value without additional attributes
250
+ */
251
+ USER_INDEX_SK: (type, value) => {
252
+ return `${PkPrefix.IDENTITY}${type}#${value}`;
253
+ }
254
+ };
255
+ var KeyExtractor = {
256
+ // ============================================
257
+ // PK Extractors
258
+ // ============================================
259
+ /**
260
+ * Extract userId from User PK
261
+ * Pattern: USR#{userId} -> userId
262
+ */
263
+ userId: (pk) => pk.slice(PkPrefix.USER.length),
264
+ /**
265
+ * Extract sessionId from Session PK
266
+ * Pattern: SES#{sessionId} -> sessionId
267
+ */
268
+ sessionId: (pk) => pk.slice(PkPrefix.SESSION.length),
269
+ /**
270
+ * Extract provider and externalId from Provider PK
271
+ * Pattern: PRV#{provider}#{externalId} -> { provider, externalId }
272
+ * Example: PRV#google#123456789 -> { provider: 'google', externalId: '123456789' }
273
+ */
274
+ providerPKInfo: (pk) => {
275
+ const remainder = pk.slice(PkPrefix.PROVIDER.length);
276
+ const delimiterIndex = remainder.indexOf("#");
277
+ if (delimiterIndex === -1) return null;
278
+ const provider = remainder.slice(0, delimiterIndex);
279
+ const externalId = remainder.slice(delimiterIndex + 1);
280
+ return { provider, externalId };
281
+ },
282
+ // ============================================
283
+ // SK Extractors
284
+ // ============================================
285
+ /**
286
+ * Extract provider and externalId from Provider SK
287
+ * Pattern: PRV#{provider}#{externalId} -> { provider, externalId }
288
+ */
289
+ providerInfo: (sk) => {
290
+ const remainder = sk.slice(SkPrefix.PROVIDER.length);
291
+ const delimiterIndex = remainder.indexOf(DELIMITER);
292
+ if (delimiterIndex === -1) return null;
293
+ const provider = remainder.slice(0, delimiterIndex);
294
+ const externalId = remainder.slice(delimiterIndex + 1);
295
+ return { provider, externalId };
296
+ },
297
+ /**
298
+ * Extract method and optional id from MFA SK
299
+ * Pattern: MFA#{method} or MFA#{method}#{id} -> { method, id? }
300
+ * Example: MFA#totp -> { method: 'totp' }
301
+ * Example: MFA#webauthn#key_abc -> { method: 'webauthn', id: 'key_abc' }
302
+ */
303
+ mfaSKInfo: (sk) => {
304
+ const remainder = sk.slice(SkPrefix.MFA.length);
305
+ const delimiterIndex = remainder.indexOf(DELIMITER);
306
+ if (delimiterIndex === -1) {
307
+ return { method: remainder };
308
+ }
309
+ const method = remainder.slice(0, delimiterIndex);
310
+ const id = remainder.slice(delimiterIndex + 1);
311
+ return { method, id };
312
+ },
313
+ // ============================================
314
+ // Tenant Extractors (SCHEMA_V4)
315
+ // ============================================
316
+ /**
317
+ * Extract tenantId from TenantMember PK
318
+ * Pattern: TNT#{tenantId} -> tenantId
319
+ * Example: TNT#org-abc -> org-abc
320
+ */
321
+ tenantId: (pk) => pk.slice(PkPrefix.TENANT.length),
322
+ /**
323
+ * Extract userId from TenantMember SK
324
+ * Pattern: USR#{userId} -> userId
325
+ * Example: USR#01HQZX8V9ABCDEFGHIJK -> 01HQZX8V9ABCDEFGHIJK
326
+ */
327
+ tenantMemberUserId: (sk) => sk.slice(PkPrefix.USER.length),
328
+ /**
329
+ * Extract challengeId from Challenge PK
330
+ * Pattern: CHL#{challengeId} -> challengeId
331
+ * Example: CHL#abc123xyz -> abc123xyz
332
+ */
333
+ challengeIdFromPK: (pk) => pk.slice(PkPrefix.CHALLENGE.length),
334
+ /**
335
+ * Extract type and value from Identity PK
336
+ * Pattern: IDT#{type}#{value} -> { type, value }
337
+ * Example: IDT#email#user@example.com -> { type: 'email', value: 'user@example.com' }
338
+ */
339
+ identityPKInfo: (pk) => {
340
+ const remainder = pk.slice(PkPrefix.IDENTITY.length);
341
+ const delimiterIndex = remainder.indexOf("#");
342
+ if (delimiterIndex === -1) return null;
343
+ const type = remainder.slice(0, delimiterIndex);
344
+ const value = remainder.slice(delimiterIndex + 1);
345
+ return { type, value };
346
+ },
347
+ /**
348
+ * Extract credentialId from Credential PK
349
+ * Pattern: CRD#{credentialId} -> credentialId
350
+ * Example: CRD#cred_01JCQR8XMZP2KGH7NWV8F9E3TS -> cred_01JCQR8XMZP2KGH7NWV8F9E3TS
351
+ */
352
+ credentialId: (pk) => pk.slice(PkPrefix.CREDENTIAL.length),
353
+ /**
354
+ * Extract type from Credential User GSI SK
355
+ * Pattern: CRD#{type}#{credentialId} -> type
356
+ * Example: CRD#password#cred_abc -> password
357
+ */
358
+ credentialTypeFromUserSK: (sk) => {
359
+ const remainder = sk.slice(PkPrefix.CREDENTIAL.length);
360
+ const delimiterIndex = remainder.indexOf("#");
361
+ if (delimiterIndex === -1) return "";
362
+ return remainder.slice(0, delimiterIndex);
363
+ },
364
+ /**
365
+ * Extract type and value from User Index SK (GSI)
366
+ * Pattern: IDT#{type}#{value} -> { type, value }
367
+ * Example: IDT#email#user@example.com -> { type: 'email', value: 'user@example.com' }
368
+ */
369
+ userIndexSKInfo: (sk) => {
370
+ const remainder = sk.slice(PkPrefix.IDENTITY.length);
371
+ const delimiterIndex = remainder.indexOf("#");
372
+ if (delimiterIndex === -1) return null;
373
+ const type = remainder.slice(0, delimiterIndex);
374
+ const value = remainder.slice(delimiterIndex + 1);
375
+ return { type, value };
376
+ }
377
+ };
378
+ var GSIKeys = {
379
+ // ============================================
380
+ // GSI_ActiveSessions
381
+ // ============================================
382
+ /**
383
+ * Active Sessions GSI PK
384
+ * Pattern: USR#{userId} (user-partitioned for scalability)
385
+ * Used by: GSI_ActiveSessions
386
+ * NOTE: Changed from global 'ACTIVE' to user-partitioned to prevent hot partition
387
+ */
388
+ ACTIVE_SESSION_PK: (userId) => `${PkPrefix.USER}${userId}`,
389
+ /**
390
+ * Active Sessions GSI SK
391
+ * Pattern: {createdAt}#{sessionId}
392
+ * Used by: GSI_ActiveSessions
393
+ */
394
+ ACTIVE_SESSION_SK: (createdAt, sessionId) => `${createdAt}${DELIMITER}${sessionId}`,
395
+ // ============================================
396
+ // GSI_UserSessions
397
+ // ============================================
398
+ /**
399
+ * User Sessions GSI PK
400
+ * Pattern: USR#{userId}
401
+ * Used by: GSI_UserSessions
402
+ */
403
+ USER_SESSION_PK: (userId) => `${PkPrefix.USER}${userId}`,
404
+ /**
405
+ * User Sessions GSI SK
406
+ * Pattern: {createdAt}#{sessionId}
407
+ * Used by: GSI_UserSessions
408
+ */
409
+ USER_SESSION_SK: (createdAt, sessionId) => `${createdAt}${DELIMITER}${sessionId}`,
410
+ // ============================================
411
+ // GSI_UsersByStatus
412
+ // ============================================
413
+ /**
414
+ * Users By Status GSI PK
415
+ * Pattern: USR#ACTIVE or USR#INACTIVE (SCHEMA_V4)
416
+ * Used by: GSI_UsersByStatus
417
+ * Maps: active → ACTIVE, suspended/deleted → INACTIVE
418
+ */
419
+ USER_STATUS_PK: (status) => {
420
+ const statusGroup = status === USER_STATUS.ACTIVE ? "ACTIVE" : "INACTIVE";
421
+ return `${EntityPrefix.USER}${DELIMITER}${statusGroup}`;
422
+ },
423
+ /**
424
+ * Users By Status GSI SK
425
+ * Pattern: {userId}
426
+ * Used by: GSI_UsersByStatus
427
+ * Simple userId pattern to avoid needing createdAt for syncAuthStatus
428
+ */
429
+ USER_STATUS_SK: (userId) => userId,
430
+ // ============================================
431
+ // GSI_UserProviders
432
+ // ============================================
433
+ /**
434
+ * User Providers GSI PK
435
+ * Pattern: USR#{userId}
436
+ * Used by: GSI_UserProviders
437
+ */
438
+ USER_PROVIDER_PK: (userId) => `${PkPrefix.USER}${userId}`,
439
+ /**
440
+ * User Providers GSI SK
441
+ * Pattern: PRV#{provider}#{externalId}
442
+ * Used by: GSI_UserProviders
443
+ */
444
+ USER_PROVIDER_SK: (provider, externalId) => `${SkPrefix.PROVIDER}${provider}${DELIMITER}${externalId}`
445
+ };
446
+ var TableAttr = {
447
+ PK: "PK",
448
+ SK: "SK",
449
+ TTL: "TTL",
450
+ ACTIVE_SESSION_PK: "activeSessionPk",
451
+ // GSI partition key for active sessions
452
+ ACTIVE_SESSION_SK: "activeSessionSk",
453
+ // GSI sort key for active sessions (createdAt#sessionId)
454
+ USER_SESSION_PK: "userSessionPk",
455
+ // GSI partition key for user sessions (USR#{userId})
456
+ USER_SESSION_SK: "userSessionSk",
457
+ // GSI sort key for user sessions (createdAt#sessionId)
458
+ USER_STATUS_PK: "userStatusPk",
459
+ // GSI partition key for users by status
460
+ USER_STATUS_SK: "userStatusSk",
461
+ // GSI sort key for users by status (userId)
462
+ USER_PROVIDER_PK: "userProviderPk",
463
+ // GSI partition key for user providers (USR#{userId})
464
+ USER_PROVIDER_SK: "userProviderSk",
465
+ // GSI sort key for user providers (PRV#{provider}#{externalId})
466
+ // Challenge attributes
467
+ USER_ID: "userId",
468
+ TENANT_ID: "tenantId",
469
+ AUDIENCE: "audience",
470
+ PURPOSE: "purpose",
471
+ CHALLENGE_STATUS: "challengeStatus",
472
+ // Named challengeStatus to avoid DynamoDB reserved keyword 'status'
473
+ PHASE: "phase",
474
+ ALLOWED_METHODS: "allowedMethods",
475
+ METHOD: "method",
476
+ IDENTITY_TYPE: "identityType",
477
+ // For credential challenges
478
+ IDENTITY_VALUE: "identityValue",
479
+ // For credential challenges
480
+ CODE_HASH: "codeHash",
481
+ ATTEMPTS: "attempts",
482
+ MAX_ATTEMPTS: "maxAttempts",
483
+ METADATA: "metadata",
484
+ FLOW_KEY: "flowKey",
485
+ EXPIRES_AT: "expiresAt",
486
+ CREATED_AT: "createdAt",
487
+ // Session attributes
488
+ TOKEN_ID: "tokenId",
489
+ LAST_USED_AT: "lastUsedAt",
490
+ REVOKED_AT: "revokedAt",
491
+ PERM_MASK: "permMask",
492
+ ROLE_IDS: "roleIds",
493
+ DEVICE_LABEL: "deviceLabel",
494
+ COUNTRY: "country",
495
+ CITY: "city",
496
+ // Identity attributes
497
+ IDENTITY_USER_PK: "identityUserPk",
498
+ // GSI partition key for user identities (USR#{userId})
499
+ IDENTITY_USER_SK: "identityUserSk",
500
+ // GSI sort key for user identities (IDT#{type}#{value})
501
+ LOGIN_ENABLED: "loginEnabled",
502
+ VERIFIED_AT: "verifiedAt",
503
+ GUARDS: "guards",
504
+ UPDATED_AT: "updatedAt",
505
+ // Credential attributes
506
+ CREDENTIAL_USER_PK: "credentialUserPk",
507
+ // GSI partition key for user credentials (USR#{userId})
508
+ CREDENTIAL_USER_SK: "credentialUserSk",
509
+ // GSI sort key for user credentials (CRD#{type}#{credentialId})
510
+ CREDENTIAL_VALUE: "value"
511
+ // Credential value (password hash, etc.)
512
+ // Note: REVOKED_AT is shared with Session (defined above)
513
+ };
514
+ var GSIName = {
515
+ ACTIVE_SESSIONS: "GSI_ActiveSessions",
516
+ USER_SESSIONS: "GSI_UserSessions",
517
+ USERS_BY_STATUS: "GSI_UsersByStatus",
518
+ USER_PROVIDERS: "GSI_UserProviders",
519
+ USER_IDENTITIES: "GSI_UserIdentities",
520
+ // Query all identities for a user
521
+ USER_CREDENTIALS: "GSI_UserCredentials"
522
+ // Query all credentials for a user
523
+ };
524
+ function millisToDate(millis) {
525
+ return new Date(millis);
526
+ }
527
+
528
+ // src/dynamodb-tenant-member-repo.ts
10
529
  var DynamoDBTenantMemberRepository = class {
11
530
  constructor(client, tableName) {
12
531
  this.client = client;
@@ -16,14 +535,14 @@ var DynamoDBTenantMemberRepository = class {
16
535
  });
17
536
  }
18
537
  docClient;
19
- async get(tenantId, audience, userId) {
538
+ async get(tenantId, userId) {
20
539
  try {
21
540
  const response = await this.docClient.send(
22
541
  new libDynamodb.GetCommand({
23
542
  TableName: this.tableName,
24
543
  Key: {
25
- [databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
26
- [databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
544
+ [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
545
+ [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId)
27
546
  }
28
547
  })
29
548
  );
@@ -42,8 +561,8 @@ var DynamoDBTenantMemberRepository = class {
42
561
  new libDynamodb.PutCommand({
43
562
  TableName: this.tableName,
44
563
  Item: {
45
- [databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(data.tenantId),
46
- [databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(data.audience, data.userId),
564
+ [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(data.tenantId),
565
+ [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(data.userId),
47
566
  userStatus: data.status,
48
567
  roleIds: JSON.stringify(data.roleIds),
49
568
  permMask: data.permMask ?? 0,
@@ -61,7 +580,7 @@ var DynamoDBTenantMemberRepository = class {
61
580
  return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
62
581
  }
63
582
  }
64
- async updatePermissions(tenantId, audience, userId, updates) {
583
+ async updatePermissions(tenantId, userId, updates) {
65
584
  try {
66
585
  const setClauses = ["#updatedAt = :updatedAt"];
67
586
  const names = { "#updatedAt": "updatedAt" };
@@ -80,8 +599,8 @@ var DynamoDBTenantMemberRepository = class {
80
599
  new libDynamodb.UpdateCommand({
81
600
  TableName: this.tableName,
82
601
  Key: {
83
- [databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
84
- [databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
602
+ [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
603
+ [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId)
85
604
  },
86
605
  UpdateExpression: `SET ${setClauses.join(", ")}`,
87
606
  ExpressionAttributeNames: names,
@@ -97,14 +616,14 @@ var DynamoDBTenantMemberRepository = class {
97
616
  return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
98
617
  }
99
618
  }
100
- async updateStatus(tenantId, audience, userId, status) {
619
+ async updateStatus(tenantId, userId, status) {
101
620
  try {
102
621
  await this.docClient.send(
103
622
  new libDynamodb.UpdateCommand({
104
623
  TableName: this.tableName,
105
624
  Key: {
106
- [databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
107
- [databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
625
+ [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
626
+ [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId)
108
627
  },
109
628
  UpdateExpression: "SET #status = :status, #updatedAt = :updatedAt",
110
629
  ExpressionAttributeNames: {
@@ -126,14 +645,14 @@ var DynamoDBTenantMemberRepository = class {
126
645
  return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
127
646
  }
128
647
  }
129
- async remove(tenantId, audience, userId) {
648
+ async remove(tenantId, userId) {
130
649
  try {
131
650
  await this.docClient.send(
132
651
  new libDynamodb.DeleteCommand({
133
652
  TableName: this.tableName,
134
653
  Key: {
135
- [databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
136
- [databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
654
+ [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
655
+ [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId)
137
656
  }
138
657
  })
139
658
  );
@@ -143,17 +662,16 @@ var DynamoDBTenantMemberRepository = class {
143
662
  }
144
663
  }
145
664
  mapItemToEntity(item) {
146
- const tenantId = databasePluginDynamodb.KeyExtractor.tenantId(item[databasePluginDynamodb.TableAttr.PK]);
147
- const skInfo = databasePluginDynamodb.KeyExtractor.tenantMemberSKInfo(item[databasePluginDynamodb.TableAttr.SK]);
665
+ const tenantId = KeyExtractor.tenantId(item[TableAttr.PK]);
666
+ const userId = KeyExtractor.tenantMemberUserId(item[TableAttr.SK]);
148
667
  return {
149
668
  tenantId,
150
- audience: skInfo?.audience ?? "",
151
- userId: skInfo?.userId ?? "",
669
+ userId,
152
670
  status: item["userStatus"],
153
671
  roleIds: JSON.parse(item["roleIds"]),
154
672
  permMask: item["permMask"] ?? 0,
155
- createdAt: databasePluginDynamodb.millisToDate(item["createdAt"]),
156
- updatedAt: databasePluginDynamodb.millisToDate(item["updatedAt"])
673
+ createdAt: millisToDate(item["createdAt"]),
674
+ updatedAt: millisToDate(item["updatedAt"])
157
675
  };
158
676
  }
159
677
  };
@@ -180,22 +698,22 @@ var DynamoDBSessionRepository = class {
180
698
  const response = await this.docClient.send(
181
699
  new libDynamodb.QueryCommand({
182
700
  TableName: this.tableName,
183
- IndexName: databasePluginDynamodb.GSIName.ACTIVE_SESSIONS,
184
- KeyConditionExpression: `${databasePluginDynamodb.TableAttr.ACTIVE_SESSION_PK} = :activePk`,
701
+ IndexName: GSIName.ACTIVE_SESSIONS,
702
+ KeyConditionExpression: `${TableAttr.ACTIVE_SESSION_PK} = :activePk`,
185
703
  ExpressionAttributeValues: {
186
- ":activePk": databasePluginDynamodb.GSIKeys.ACTIVE_SESSION_PK(userId)
704
+ ":activePk": GSIKeys.ACTIVE_SESSION_PK(userId)
187
705
  },
188
- ProjectionExpression: `${databasePluginDynamodb.TableAttr.PK}, ${databasePluginDynamodb.TableAttr.TENANT_ID}, ${databasePluginDynamodb.TableAttr.AUDIENCE}, ${databasePluginDynamodb.TableAttr.USER_ID}`,
706
+ ProjectionExpression: `${TableAttr.PK}, ${TableAttr.TENANT_ID}, ${TableAttr.AUDIENCE}, ${TableAttr.USER_ID}`,
189
707
  Limit: limit,
190
708
  ExclusiveStartKey: exclusiveStartKey,
191
709
  ScanIndexForward: false
192
710
  })
193
711
  );
194
712
  const sessions = (response.Items ?? []).map((item) => ({
195
- id: databasePluginDynamodb.KeyExtractor.sessionId(item[databasePluginDynamodb.TableAttr.PK]),
196
- userId: item[databasePluginDynamodb.TableAttr.USER_ID],
197
- tenantId: item[databasePluginDynamodb.TableAttr.TENANT_ID],
198
- audience: item[databasePluginDynamodb.TableAttr.AUDIENCE]
713
+ id: KeyExtractor.sessionId(item[TableAttr.PK]),
714
+ userId: item[TableAttr.USER_ID],
715
+ tenantId: item[TableAttr.TENANT_ID],
716
+ audience: item[TableAttr.AUDIENCE]
199
717
  }));
200
718
  const hasNext = !!response.LastEvaluatedKey;
201
719
  const cursor = hasNext ? Buffer.from(JSON.stringify(response.LastEvaluatedKey)).toString("base64url") : null;
@@ -226,19 +744,19 @@ var DynamoDBSessionRepository = class {
226
744
  Update: {
227
745
  TableName: this.tableName,
228
746
  Key: {
229
- [databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.SESSION_PK(sessionId),
230
- [databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.SESSION_SK()
747
+ [TableAttr.PK]: KeyPattern.SESSION_PK(sessionId),
748
+ [TableAttr.SK]: KeyPattern.SESSION_SK()
231
749
  },
232
750
  UpdateExpression: `SET #revokedAt = :revokedAt REMOVE #activePk, #activeSk`,
233
751
  ExpressionAttributeNames: {
234
- "#revokedAt": databasePluginDynamodb.TableAttr.REVOKED_AT,
235
- "#activePk": databasePluginDynamodb.TableAttr.ACTIVE_SESSION_PK,
236
- "#activeSk": databasePluginDynamodb.TableAttr.ACTIVE_SESSION_SK
752
+ "#revokedAt": TableAttr.REVOKED_AT,
753
+ "#activePk": TableAttr.ACTIVE_SESSION_PK,
754
+ "#activeSk": TableAttr.ACTIVE_SESSION_SK
237
755
  },
238
756
  ExpressionAttributeValues: {
239
757
  ":revokedAt": revokedAt
240
758
  },
241
- ConditionExpression: `attribute_exists(${databasePluginDynamodb.TableAttr.PK}) AND attribute_not_exists(#revokedAt)`
759
+ ConditionExpression: `attribute_exists(${TableAttr.PK}) AND attribute_not_exists(#revokedAt)`
242
760
  }
243
761
  }));
244
762
  try {