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