@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 +553 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +531 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/dynamodb-tenant-member-repo.ts","../src/dynamodb-session-repo.ts","../src/index.ts"],"names":["DynamoDBDocumentClient","GetCommand","TableAttr","KeyPattern","ok","err","tenantAccessErrors","PutCommand","UpdateCommand","DeleteCommand","KeyExtractor","millisToDate","QueryCommand","GSIName","GSIKeys","TransactWriteCommand","DynamoDBClient"],"mappings":";;;;;;;;;AA8BO,IAAM,iCAAN,MAAuE;AAAA,EAG5E,WAAA,CACU,QACA,SAAA,EACR;AAFQ,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAER,IAAA,IAAA,CAAK,SAAA,GAAYA,kCAAA,CAAuB,IAAA,CAAK,MAAA,EAAQ;AAAA,MACnD,eAAA,EAAiB,EAAE,qBAAA,EAAuB,IAAA;AAAK,KAChD,CAAA;AAAA,EACH;AAAA,EATQ,SAAA;AAAA,EAWR,MAAM,GAAA,CACJ,QAAA,EACA,QAAA,EACA,MAAA,EACsC;AACtC,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA;AAAA,QACpC,IAAIC,sBAAA,CAAW;AAAA,UACb,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAACC,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAACD,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,gBAAA,CAAiB,UAAU,MAAM;AAAA;AAC9D,SACD;AAAA,OACH;AAEA,MAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,QAAA,OAAOC,iBAAG,IAAI,CAAA;AAAA,MAChB;AAEA,MAAA,OAAOA,gBAAA,CAAG,IAAA,CAAK,eAAA,CAAgB,QAAA,CAAS,IAAI,CAAC,CAAA;AAAA,IAC/C,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,IAAA,EAAqD;AAChE,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIC,sBAAA,CAAW;AAAA,UACb,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,IAAA,EAAM;AAAA,YACJ,CAACL,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,gBAAA,CAAiB,KAAK,QAAQ,CAAA;AAAA,YACzD,CAACD,iCAAU,EAAE,GAAGC,kCAAW,gBAAA,CAAiB,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,MAAM,CAAA;AAAA,YACtE,YAAY,IAAA,CAAK,MAAA;AAAA,YACjB,OAAA,EAAS,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,OAAO,CAAA;AAAA,YACpC,QAAA,EAAU,KAAK,QAAA,IAAY,CAAA;AAAA,YAC3B,SAAA,EAAW,GAAA;AAAA,YACX,SAAA,EAAW;AAAA,WACb;AAAA,UACA,mBAAA,EAAqB;AAAA,SACtB;AAAA,OACH;AAEA,MAAA,OAAOC,gBAAA,EAAG;AAAA,IACZ,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAK,KAAA,CAA4B,SAAS,iCAAA,EAAmC;AAC3E,QAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,qBAAA,EAAuB,CAAA;AAAA,MACvD;AACA,MAAA,OAAOD,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,iBAAA,CACJ,QAAA,EACA,QAAA,EACA,QACA,OAAA,EACuB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,UAAA,GAAuB,CAAC,yBAAyB,CAAA;AACvD,MAAA,MAAM,KAAA,GAAgC,EAAE,YAAA,EAAc,WAAA,EAAY;AAClE,MAAA,MAAM,MAAA,GAAkC,EAAE,YAAA,EAAc,IAAA,CAAK,KAAI,EAAE;AAEnE,MAAA,IAAI,OAAA,CAAQ,YAAY,KAAA,CAAA,EAAW;AACjC,QAAA,UAAA,CAAW,KAAK,qBAAqB,CAAA;AACrC,QAAA,KAAA,CAAM,UAAU,CAAA,GAAI,SAAA;AACpB,QAAA,MAAA,CAAO,UAAU,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,QAAQ,OAAO,CAAA;AAAA,MACrD;AAEA,MAAA,IAAI,OAAA,CAAQ,aAAa,KAAA,CAAA,EAAW;AAClC,QAAA,UAAA,CAAW,KAAK,uBAAuB,CAAA;AACvC,QAAA,KAAA,CAAM,WAAW,CAAA,GAAI,UAAA;AACrB,QAAA,MAAA,CAAO,WAAW,IAAI,OAAA,CAAQ,QAAA;AAAA,MAChC;AAEA,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIE,yBAAA,CAAc;AAAA,UAChB,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAACN,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAACD,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,gBAAA,CAAiB,UAAU,MAAM;AAAA,WAC9D;AAAA,UACA,gBAAA,EAAkB,CAAA,IAAA,EAAO,UAAA,CAAW,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAAA,UAC9C,wBAAA,EAA0B,KAAA;AAAA,UAC1B,yBAAA,EAA2B,MAAA;AAAA,UAC3B,mBAAA,EAAqB;AAAA,SACtB;AAAA,OACH;AAEA,MAAA,OAAOC,gBAAA,EAAG;AAAA,IACZ,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAK,KAAA,CAA4B,SAAS,iCAAA,EAAmC;AAC3E,QAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,gBAAA,EAAkB,CAAA;AAAA,MAClD;AACA,MAAA,OAAOD,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,YAAA,CACJ,QAAA,EACA,QAAA,EACA,QACA,MAAA,EACuB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIE,yBAAA,CAAc;AAAA,UAChB,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAACN,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAACD,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,gBAAA,CAAiB,UAAU,MAAM;AAAA,WAC9D;AAAA,UACA,gBAAA,EAAkB,gDAAA;AAAA,UAClB,wBAAA,EAA0B;AAAA,YACxB,SAAA,EAAW,YAAA;AAAA,YACX,YAAA,EAAc;AAAA,WAChB;AAAA,UACA,yBAAA,EAA2B;AAAA,YACzB,SAAA,EAAW,MAAA;AAAA,YACX,YAAA,EAAc,KAAK,GAAA;AAAI,WACzB;AAAA,UACA,mBAAA,EAAqB;AAAA,SACtB;AAAA,OACH;AAEA,MAAA,OAAOC,gBAAA,EAAG;AAAA,IACZ,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAK,KAAA,CAA4B,SAAS,iCAAA,EAAmC;AAC3E,QAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,gBAAA,EAAkB,CAAA;AAAA,MAClD;AACA,MAAA,OAAOD,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,MAAA,CAAO,QAAA,EAAoB,QAAA,EAAkB,MAAA,EAAuC;AACxF,IAAA,IAAI;AACF,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIG,yBAAA,CAAc;AAAA,UAChB,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAACP,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAACD,gCAAA,CAAU,EAAE,GAAGC,iCAAA,CAAW,gBAAA,CAAiB,UAAU,MAAM;AAAA;AAC9D,SACD;AAAA,OACH;AAEA,MAAA,OAAOC,gBAAA,EAAG;AAAA,IACZ,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,gBAAgB,IAAA,EAA6C;AACnE,IAAA,MAAM,WAAWI,mCAAA,CAAa,QAAA,CAAS,IAAA,CAAKR,gCAAA,CAAU,EAAE,CAAW,CAAA;AACnE,IAAA,MAAM,SAASQ,mCAAA,CAAa,kBAAA,CAAmB,IAAA,CAAKR,gCAAA,CAAU,EAAE,CAAW,CAAA;AAE3E,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,QAAA,EAAU,QAAQ,QAAA,IAAY,EAAA;AAAA,MAC9B,MAAA,EAAS,QAAQ,MAAA,IAAU,EAAA;AAAA,MAC3B,MAAA,EAAQ,KAAK,YAAY,CAAA;AAAA,MACzB,OAAA,EAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,SAAS,CAAW,CAAA;AAAA,MAC7C,QAAA,EAAW,IAAA,CAAK,UAAU,CAAA,IAAgB,CAAA;AAAA,MAC1C,SAAA,EAAWS,mCAAA,CAAa,IAAA,CAAK,WAAW,CAAW,CAAA;AAAA,MACnD,SAAA,EAAWA,mCAAA,CAAa,IAAA,CAAK,WAAW,CAAW;AAAA,KACrD;AAAA,EACF;AACF;ACrLO,IAAM,4BAAN,MAA6D;AAAA,EAGlE,WAAA,CACU,QACA,SAAA,EACR;AAFQ,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAER,IAAA,IAAA,CAAK,SAAA,GAAYX,kCAAAA,CAAuB,IAAA,CAAK,MAAM,CAAA;AAAA,EACrD;AAAA,EAPQ,SAAA;AAAA,EASR,MAAM,kBAAA,CACJ,MAAA,EACA,OAAA,EACuC;AACvC,IAAA,IAAI;AACF,MAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,KAAA,IAAS,IAAI,GAAG,CAAA;AAChD,MAAA,IAAI,iBAAA;AAEJ,MAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,QAAA,IAAI;AACF,UAAA,iBAAA,GAAoB,IAAA,CAAK,KAAA;AAAA,YACvB,OAAO,IAAA,CAAK,OAAA,CAAQ,QAAQ,WAAW,CAAA,CAAE,SAAS,OAAO;AAAA,WAC3D;AAAA,QACF,CAAA,CAAA,MAAQ;AACN,UAAA,OAAOK,iBAAAA,CAAIC,sCAAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,QAChD;AAAA,MACF;AAEA,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA;AAAA,QACpC,IAAIM,wBAAA,CAAa;AAAA,UACf,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,WAAWC,8BAAA,CAAQ,eAAA;AAAA,UACnB,sBAAA,EAAwB,CAAA,EAAGX,gCAAAA,CAAU,iBAAiB,CAAA,YAAA,CAAA;AAAA,UACtD,yBAAA,EAA2B;AAAA,YACzB,WAAA,EAAaY,8BAAA,CAAQ,iBAAA,CAAkB,MAAM;AAAA,WAC/C;AAAA,UACA,oBAAA,EAAsB,CAAA,EAAGZ,gCAAAA,CAAU,EAAE,CAAA,EAAA,EAAKA,gCAAAA,CAAU,SAAS,CAAA,EAAA,EAAKA,gCAAAA,CAAU,QAAQ,CAAA,EAAA,EAAKA,gCAAAA,CAAU,OAAO,CAAA,CAAA;AAAA,UAC1G,KAAA,EAAO,KAAA;AAAA,UACP,iBAAA,EAAmB,iBAAA;AAAA,UACnB,gBAAA,EAAkB;AAAA,SACnB;AAAA,OACH;AAEA,MAAA,MAAM,YAA2B,QAAA,CAAS,KAAA,IAAS,EAAC,EAAG,GAAA,CAAI,CAAC,IAAA,MAAU;AAAA,QACpE,IAAIQ,mCAAAA,CAAa,SAAA,CAAU,IAAA,CAAKR,gCAAAA,CAAU,EAAE,CAAW,CAAA;AAAA,QACvD,MAAA,EAAQ,IAAA,CAAKA,gCAAAA,CAAU,OAAO,CAAA;AAAA,QAC9B,QAAA,EAAU,IAAA,CAAKA,gCAAAA,CAAU,SAAS,CAAA;AAAA,QAClC,QAAA,EAAU,IAAA,CAAKA,gCAAAA,CAAU,QAAQ;AAAA,OACnC,CAAE,CAAA;AAEF,MAAA,MAAM,OAAA,GAAU,CAAC,CAAC,QAAA,CAAS,gBAAA;AAC3B,MAAA,MAAM,MAAA,GAAS,OAAA,GACX,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,QAAA,CAAS,gBAAgB,CAAC,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,GAC3E,IAAA;AAEJ,MAAA,MAAM,UAAA,GAA+B;AAAA,QACnC,IAAA,EAAM,QAAA;AAAA,QACN,KAAA;AAAA,QACA,OAAO,QAAA,CAAS,MAAA;AAAA,QAChB,OAAA;AAAA,QACA,OAAA,EAAS,CAAC,CAAC,OAAA,EAAS,MAAA;AAAA,QACpB;AAAA,OACF;AAEA,MAAA,OAAOE,gBAAAA,CAAG,QAAA,EAAU,EAAE,UAAA,EAAY,CAAA;AAAA,IACpC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAAA,CAAIC,sCAAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,UAAA,EAAkD;AAClE,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAOF,iBAAG,CAAC,CAAA;AAAA,IACb;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,MAAM,SAAA,GAAY,EAAA;AAElB,MAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,UAAA,CAAW,MAAA,EAAQ,KAAK,SAAA,EAAW;AACrD,QAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,CAAA,EAAG,IAAI,SAAS,CAAA;AAE/C,QAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,GAAA,CAAI,CAAC,SAAA,MAAe;AAAA,UAC9C,MAAA,EAAQ;AAAA,YACN,WAAW,IAAA,CAAK,SAAA;AAAA,YAChB,GAAA,EAAK;AAAA,cACH,CAACF,gCAAAA,CAAU,EAAE,GAAGC,iCAAAA,CAAW,WAAW,SAAS,CAAA;AAAA,cAC/C,CAACD,gCAAAA,CAAU,EAAE,GAAGC,kCAAW,UAAA;AAAW,aACxC;AAAA,YACA,gBAAA,EAAkB,CAAA,uDAAA,CAAA;AAAA,YAClB,wBAAA,EAA0B;AAAA,cACxB,cAAcD,gCAAAA,CAAU,UAAA;AAAA,cACxB,aAAaA,gCAAAA,CAAU,iBAAA;AAAA,cACvB,aAAaA,gCAAAA,CAAU;AAAA,aACzB;AAAA,YACA,yBAAA,EAA2B;AAAA,cACzB,YAAA,EAAc;AAAA,aAChB;AAAA,YACA,mBAAA,EAAqB,CAAA,iBAAA,EAAoBA,gCAAAA,CAAU,EAAE,CAAA,sCAAA;AAAA;AACvD,SACF,CAAE,CAAA;AAEF,QAAA,IAAI;AACF,UAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,YACnB,IAAIa,gCAAA,CAAqB,EAAE,aAAA,EAAe,eAAe;AAAA,WAC3D;AACA,UAAA,KAAA,IAAS,KAAA,CAAM,MAAA;AAAA,QACjB,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF;AAEA,MAAA,OAAOX,iBAAG,KAAK,CAAA;AAAA,IACjB,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAAA,CAAIC,sCAAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AACF;;;ACrHO,SAAS,iCACd,MAAA,EACyB;AACzB,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA,IAAU,IAAIU,6BAAAA,CAAe,EAAE,CAAA;AAErD,EAAA,OAAO;AAAA,IACL,sBAAA,EAAwB,IAAI,8BAAA,CAA+B,MAAA,EAAQ,OAAO,SAAS,CAAA;AAAA,IACnF,iBAAA,EAAmB,IAAI,yBAAA,CAA0B,MAAA,EAAQ,OAAO,SAAS;AAAA,GAC3E;AACF","file":"index.cjs","sourcesContent":["/**\r\n * DynamoDB TenantMember Repository Implementation\r\n */\r\n\r\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\r\nimport {\r\n DynamoDBDocumentClient,\r\n GetCommand,\r\n PutCommand,\r\n UpdateCommand,\r\n DeleteCommand,\r\n} from '@aws-sdk/lib-dynamodb';\r\nimport { ok, err, type Result } from 'ts-micro-result';\r\nimport type {\r\n TenantMemberRepository,\r\n TenantMember,\r\n TenantMemberStatus,\r\n CreateTenantMemberData,\r\n UpdateTenantMemberPermissionsData,\r\n TenantId,\r\n UserId,\r\n} from '@auth-craft/tenant-access-control';\r\nimport { tenantAccessErrors } from '@auth-craft/tenant-access-control';\r\nimport {\r\n KeyPattern,\r\n KeyExtractor,\r\n TableAttr,\r\n millisToDate,\r\n} from '@auth-craft/database-plugin-dynamodb';\r\n\r\nexport class DynamoDBTenantMemberRepository implements TenantMemberRepository {\r\n private docClient: DynamoDBDocumentClient;\r\n\r\n constructor(\r\n private client: DynamoDBClient,\r\n private tableName: string\r\n ) {\r\n this.docClient = DynamoDBDocumentClient.from(client, {\r\n marshallOptions: { removeUndefinedValues: true },\r\n });\r\n }\r\n\r\n async get(\r\n tenantId: TenantId,\r\n audience: string,\r\n userId: UserId\r\n ): Promise<Result<TenantMember | null>> {\r\n try {\r\n const response = await this.docClient.send(\r\n new GetCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId),\r\n },\r\n })\r\n );\r\n\r\n if (!response.Item) {\r\n return ok(null);\r\n }\r\n\r\n return ok(this.mapItemToEntity(response.Item));\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async create(data: CreateTenantMemberData): Promise<Result<void>> {\r\n try {\r\n const now = Date.now();\r\n\r\n await this.docClient.send(\r\n new PutCommand({\r\n TableName: this.tableName,\r\n Item: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(data.tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(data.audience, data.userId),\r\n userStatus: data.status,\r\n roleIds: JSON.stringify(data.roleIds),\r\n permMask: data.permMask ?? 0,\r\n createdAt: now,\r\n updatedAt: now,\r\n },\r\n ConditionExpression: 'attribute_not_exists(PK)',\r\n })\r\n );\r\n\r\n return ok();\r\n } catch (error: unknown) {\r\n if ((error as { name?: string }).name === 'ConditionalCheckFailedException') {\r\n return err(tenantAccessErrors.MEMBER_ALREADY_EXISTS());\r\n }\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async updatePermissions(\r\n tenantId: TenantId,\r\n audience: string,\r\n userId: UserId,\r\n updates: UpdateTenantMemberPermissionsData\r\n ): Promise<Result<void>> {\r\n try {\r\n const setClauses: string[] = ['#updatedAt = :updatedAt'];\r\n const names: Record<string, string> = { '#updatedAt': 'updatedAt' };\r\n const values: Record<string, unknown> = { ':updatedAt': Date.now() };\r\n\r\n if (updates.roleIds !== undefined) {\r\n setClauses.push('#roleIds = :roleIds');\r\n names['#roleIds'] = 'roleIds';\r\n values[':roleIds'] = JSON.stringify(updates.roleIds);\r\n }\r\n\r\n if (updates.permMask !== undefined) {\r\n setClauses.push('#permMask = :permMask');\r\n names['#permMask'] = 'permMask';\r\n values[':permMask'] = updates.permMask;\r\n }\r\n\r\n await this.docClient.send(\r\n new UpdateCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId),\r\n },\r\n UpdateExpression: `SET ${setClauses.join(', ')}`,\r\n ExpressionAttributeNames: names,\r\n ExpressionAttributeValues: values,\r\n ConditionExpression: 'attribute_exists(PK)',\r\n })\r\n );\r\n\r\n return ok();\r\n } catch (error: unknown) {\r\n if ((error as { name?: string }).name === 'ConditionalCheckFailedException') {\r\n return err(tenantAccessErrors.MEMBER_NOT_FOUND());\r\n }\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async updateStatus(\r\n tenantId: TenantId,\r\n audience: string,\r\n userId: UserId,\r\n status: TenantMemberStatus\r\n ): Promise<Result<void>> {\r\n try {\r\n await this.docClient.send(\r\n new UpdateCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId),\r\n },\r\n UpdateExpression: 'SET #status = :status, #updatedAt = :updatedAt',\r\n ExpressionAttributeNames: {\r\n '#status': 'userStatus',\r\n '#updatedAt': 'updatedAt',\r\n },\r\n ExpressionAttributeValues: {\r\n ':status': status,\r\n ':updatedAt': Date.now(),\r\n },\r\n ConditionExpression: 'attribute_exists(PK)',\r\n })\r\n );\r\n\r\n return ok();\r\n } catch (error: unknown) {\r\n if ((error as { name?: string }).name === 'ConditionalCheckFailedException') {\r\n return err(tenantAccessErrors.MEMBER_NOT_FOUND());\r\n }\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async remove(tenantId: TenantId, audience: string, userId: UserId): Promise<Result<void>> {\r\n try {\r\n await this.docClient.send(\r\n new DeleteCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId),\r\n },\r\n })\r\n );\r\n\r\n return ok();\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n private mapItemToEntity(item: Record<string, unknown>): TenantMember {\r\n const tenantId = KeyExtractor.tenantId(item[TableAttr.PK] as string) as TenantId;\r\n const skInfo = KeyExtractor.tenantMemberSKInfo(item[TableAttr.SK] as string);\r\n\r\n return {\r\n tenantId,\r\n audience: skInfo?.audience ?? '',\r\n userId: (skInfo?.userId ?? '') as UserId,\r\n status: item['userStatus'] as TenantMemberStatus,\r\n roleIds: JSON.parse(item['roleIds'] as string),\r\n permMask: (item['permMask'] as number) ?? 0,\r\n createdAt: millisToDate(item['createdAt'] as number),\r\n updatedAt: millisToDate(item['updatedAt'] as number),\r\n };\r\n }\r\n}\r\n","/**\r\n * DynamoDB Session Repository Implementation (Limited)\r\n *\r\n * Only implements methods needed for tenant access control:\r\n * - findActiveByUserId: Query active sessions\r\n * - revokeBatch: Batch revoke sessions\r\n */\r\n\r\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\r\nimport {\r\n DynamoDBDocumentClient,\r\n QueryCommand,\r\n TransactWriteCommand,\r\n} from '@aws-sdk/lib-dynamodb';\r\nimport { ok, err, type Result, type PaginatedResult, type CursorPagination } from 'ts-micro-result';\r\nimport type {\r\n SessionRepository,\r\n SessionInfo,\r\n SessionId,\r\n UserId,\r\n TenantId,\r\n} from '@auth-craft/tenant-access-control';\r\nimport { tenantAccessErrors } from '@auth-craft/tenant-access-control';\r\nimport {\r\n KeyPattern,\r\n KeyExtractor,\r\n TableAttr,\r\n GSIName,\r\n GSIKeys,\r\n} from '@auth-craft/database-plugin-dynamodb';\r\n\r\nexport class DynamoDBSessionRepository implements SessionRepository {\r\n private docClient: DynamoDBDocumentClient;\r\n\r\n constructor(\r\n private client: DynamoDBClient,\r\n private tableName: string\r\n ) {\r\n this.docClient = DynamoDBDocumentClient.from(client);\r\n }\r\n\r\n async findActiveByUserId(\r\n userId: UserId,\r\n options?: { limit?: number; cursor?: string }\r\n ): Promise<PaginatedResult<SessionInfo>> {\r\n try {\r\n const limit = Math.min(options?.limit ?? 50, 100);\r\n let exclusiveStartKey: Record<string, unknown> | undefined;\r\n\r\n if (options?.cursor) {\r\n try {\r\n exclusiveStartKey = JSON.parse(\r\n Buffer.from(options.cursor, 'base64url').toString('utf-8')\r\n );\r\n } catch {\r\n return err(tenantAccessErrors.INVALID_CURSOR()) as PaginatedResult<SessionInfo>;\r\n }\r\n }\r\n\r\n const response = await this.docClient.send(\r\n new QueryCommand({\r\n TableName: this.tableName,\r\n IndexName: GSIName.ACTIVE_SESSIONS,\r\n KeyConditionExpression: `${TableAttr.ACTIVE_SESSION_PK} = :activePk`,\r\n ExpressionAttributeValues: {\r\n ':activePk': GSIKeys.ACTIVE_SESSION_PK(userId),\r\n },\r\n ProjectionExpression: `${TableAttr.PK}, ${TableAttr.TENANT_ID}, ${TableAttr.AUDIENCE}, ${TableAttr.USER_ID}`,\r\n Limit: limit,\r\n ExclusiveStartKey: exclusiveStartKey,\r\n ScanIndexForward: false,\r\n })\r\n );\r\n\r\n const sessions: SessionInfo[] = (response.Items ?? []).map((item) => ({\r\n id: KeyExtractor.sessionId(item[TableAttr.PK] as string) as SessionId,\r\n userId: item[TableAttr.USER_ID] as UserId,\r\n tenantId: item[TableAttr.TENANT_ID] as TenantId | undefined,\r\n audience: item[TableAttr.AUDIENCE] as string,\r\n }));\r\n\r\n const hasNext = !!response.LastEvaluatedKey;\r\n const cursor = hasNext\r\n ? Buffer.from(JSON.stringify(response.LastEvaluatedKey)).toString('base64url')\r\n : null;\r\n\r\n const pagination: CursorPagination = {\r\n type: 'cursor',\r\n limit,\r\n count: sessions.length,\r\n hasNext,\r\n hasPrev: !!options?.cursor,\r\n cursor,\r\n };\r\n\r\n return ok(sessions, { pagination }) as PaginatedResult<SessionInfo>;\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR()) as PaginatedResult<SessionInfo>;\r\n }\r\n }\r\n\r\n async revokeBatch(sessionIds: SessionId[]): Promise<Result<number>> {\r\n if (sessionIds.length === 0) {\r\n return ok(0);\r\n }\r\n\r\n try {\r\n const revokedAt = Date.now();\r\n let count = 0;\r\n const batchSize = 25;\r\n\r\n for (let i = 0; i < sessionIds.length; i += batchSize) {\r\n const batch = sessionIds.slice(i, i + batchSize);\r\n\r\n const transactItems = batch.map((sessionId) => ({\r\n Update: {\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.SESSION_PK(sessionId),\r\n [TableAttr.SK]: KeyPattern.SESSION_SK(),\r\n },\r\n UpdateExpression: `SET #revokedAt = :revokedAt REMOVE #activePk, #activeSk`,\r\n ExpressionAttributeNames: {\r\n '#revokedAt': TableAttr.REVOKED_AT,\r\n '#activePk': TableAttr.ACTIVE_SESSION_PK,\r\n '#activeSk': TableAttr.ACTIVE_SESSION_SK,\r\n },\r\n ExpressionAttributeValues: {\r\n ':revokedAt': revokedAt,\r\n },\r\n ConditionExpression: `attribute_exists(${TableAttr.PK}) AND attribute_not_exists(#revokedAt)`,\r\n },\r\n }));\r\n\r\n try {\r\n await this.docClient.send(\r\n new TransactWriteCommand({ TransactItems: transactItems })\r\n );\r\n count += batch.length;\r\n } catch {\r\n // Some sessions may already be revoked, continue with remaining batches\r\n }\r\n }\r\n\r\n return ok(count);\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n}\r\n","/**\r\n * @auth-craft/tenant-access-control-dynamodb\r\n *\r\n * DynamoDB implementation for tenant-access-control.\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createTenantAccessSDK } from '@auth-craft/tenant-access-control';\r\n * import { createTenantAccessDynamoDBPlugin } from '@auth-craft/tenant-access-control-dynamodb';\r\n *\r\n * // Create DynamoDB plugin\r\n * const plugin = createTenantAccessDynamoDBPlugin({\r\n * tableName: 'auth-table',\r\n * });\r\n *\r\n * // Create SDK with DynamoDB implementation\r\n * const sdk = createTenantAccessSDK(plugin);\r\n * ```\r\n */\r\n\r\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\r\nimport type { TenantAccessControlDeps } from '@auth-craft/tenant-access-control';\r\nimport { DynamoDBTenantMemberRepository } from './dynamodb-tenant-member-repo';\r\nimport { DynamoDBSessionRepository } from './dynamodb-session-repo';\r\nimport type { TenantAccessDynamoDBConfig } from './config';\r\n\r\n/**\r\n * Create DynamoDB plugin for Tenant Access Control\r\n *\r\n * @param config - DynamoDB configuration\r\n * @returns TenantAccessControlDeps - Dependencies for createTenantAccessSDK\r\n */\r\nexport function createTenantAccessDynamoDBPlugin(\r\n config: TenantAccessDynamoDBConfig\r\n): TenantAccessControlDeps {\r\n const client = config.client ?? new DynamoDBClient({});\r\n\r\n return {\r\n tenantMemberRepository: new DynamoDBTenantMemberRepository(client, config.tableName),\r\n sessionRepository: new DynamoDBSessionRepository(client, config.tableName),\r\n };\r\n}\r\n\r\n// Export config type\r\nexport type { TenantAccessDynamoDBConfig } from './config';\r\n\r\n// Export repositories for advanced use cases\r\nexport { DynamoDBTenantMemberRepository } from './dynamodb-tenant-member-repo';\r\nexport { DynamoDBSessionRepository } from './dynamodb-session-repo';\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../../auth-core/src/entities/user-auth.ts","../../database-plugin-dynamodb/src/keys.ts","../../database-plugin-dynamodb/src/date-utils.ts","../src/dynamodb-tenant-member-repo.ts","../src/dynamodb-session-repo.ts","../src/index.ts"],"names":["DynamoDBDocumentClient","GetCommand","ok","err","tenantAccessErrors","PutCommand","UpdateCommand","DeleteCommand","QueryCommand","TransactWriteCommand","DynamoDBClient"],"mappings":";;;;;;;;;;AAkBO,IAAM,WAAA,GAAc;EACzB,MAAA,EAAQ,QAGV,CAAA;;;ACLO,IAAM,YAAA,GAAe;EAC1B,IAAA,EAAM;AACR,CAAA;AAMO,IAAM,QAAA,GAAW;EACtB,IAAA,EAAM,CAAA,EAAG,aAAa,IAAI,CAAA,CAAA,CAAA;EAC1B,OAAA,EAAS,MAAA;EACT,QAAA,EAAU,MAAA;EACV,SAAA,EAAW,MAAA;EACX,QAAA,EAAU,MAAA;;EACV,UAAA,EAAY,MAAA;;;EAEZ,MAAA,EAAQ;;AACV,CAAA;AAMO,IAAM,QAAA,GAAW;EACtB,QAAA,EAAU,IAAA;;EACV,IAAA,EAAM,MAAA;;EACN,OAAA,EAAS,MAAA;;EACT,QAAA,EAAU,MAAA;EACV,GAAA,EAAK;;AACP,CAAA;AAMA,IAAM,SAAA,GAAY,GAAA;AAMX,IAAM,UAAA,GAAa;;;;;;;;AASxB,EAAA,OAAA,EAAS,CAAC,MAAA,KAAmB,CAAA,EAAG,QAAA,CAAS,IAAI,GAAG,MAAM,CAAA,CAAA;;;;;;;;;;;;;;;AAgBtD,EAAA,YAAA,EAAc,MAAM,QAAA,CAAS,IAAA;;;;;;;;;;AAW7B,EAAA,eAAA,EAAiB,MAAM,QAAA,CAAS,OAAA;;;;;;;;;;AAYhC,EAAA,WAAA,EAAa,CAAC,MAAA,KAAmB,CAAA,EAAG,QAAA,CAAS,GAAG,GAAG,MAAM,CAAA,CAAA;;;;;;;EAQzD,mBAAA,EAAqB,CAAC,MAAA,EAAgB,EAAA,KACpC,CAAA,EAAG,QAAA,CAAS,GAAG,CAAA,EAAG,MAAM,CAAA,EAAG,SAAS,CAAA,EAAG,EAAE,CAAA,CAAA;;;;;;;;;;AAY3C,EAAA,gBAAA,EAAkB,CAAC,QAAA,KAAqB,CAAA,EAAG,QAAA,CAAS,MAAM,GAAG,QAAQ,CAAA,CAAA;;;;;;;AAQrE,EAAA,gBAAA,EAAkB,CAAC,MAAA,KAAmB,CAAA,EAAG,QAAA,CAAS,IAAI,GAAG,MAAM,CAAA,CAAA;;;;;;;;;AAW/D,EAAA,UAAA,EAAY,CAAC,SAAA,KAAsB,CAAA,EAAG,QAAA,CAAS,OAAO,GAAG,SAAS,CAAA,CAAA;;;;;;;;;AAUlE,EAAA,mBAAA,EAAqB,MAAM,QAAA,CAAS,QAAA;;;;AAKpC,EAAA,UAAA,EAAY,MAAM,QAAA,CAAS,QAAA;;;;;;;;EAS3B,WAAA,EAAa,CAAC,QAAA,EAAkB,UAAA,KAC9B,CAAA,EAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,QAAQ,CAAA,EAAG,SAAS,CAAA,EAAG,UAAU,CAAA,CAAA;;;;;;;;;;EAY1D,WAAA,EAAa,CAAC,QAAA,EAAkB,UAAA,KAC9B,CAAA,EAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,QAAQ,CAAA,EAAG,SAAS,CAAA,EAAG,UAAU,CAAA,CAAA;;;;;;;;;;AAW1D,EAAA,oBAAA,EAAsB,MAAM,QAAA,CAAS,QAAA;;;;;;;;;AAWrC,EAAA,YAAA,EAAc,CAAC,WAAA,KAAwB,CAAA,EAAG,QAAA,CAAS,SAAS,GAAG,WAAW,CAAA,CAAA;;;;;;;;;AAU1E,EAAA,YAAA,EAAc,MAAM,QAAA,CAAS,QAAA;;;;;;;;;;;;;;EAgB7B,WAAA,EAAa,CAAC,MAAc,KAAA,KAA0B;AAEpD,IAAA,OAAO,GAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,IAAI,IAAI,KAAK,CAAA,CAAA;AAC7C,EAAA,CAAA;;;;;;;AAQA,EAAA,WAAA,EAAa,MAAM,QAAA,CAAS,QAAA;;;;;;;;;AAW5B,EAAA,aAAA,EAAe,CAAC,YAAA,KAAyB,CAAA,EAAG,QAAA,CAAS,UAAU,GAAG,YAAY,CAAA,CAAA;;;;;;;AAQ9E,EAAA,aAAA,EAAe,MAAM,QAAA,CAAS,QAAA;;;;;;AAO9B,EAAA,sBAAA,EAAwB,CAAC,IAAA,KAAiB,CAAA,EAAG,QAAA,CAAS,UAAU,GAAG,IAAI,CAAA,CAAA,CAAA;;;;;;;EAQvE,kBAAA,EAAoB,CAAC,MAAc,YAAA,KACjC,CAAA,EAAG,SAAS,UAAU,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,YAAY,CAAA,CAAA;;;;;;;;;;;AAa/C,EAAA,aAAA,EAAe,CAAC,MAAA,KAA2B,CAAA,EAAG,QAAA,CAAS,IAAI,GAAG,MAAM,CAAA,CAAA;;;;;;;;;;EAWpE,aAAA,EAAe,CAAC,MAAc,KAAA,KAA0B;AAEtD,IAAA,OAAO,GAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,IAAI,IAAI,KAAK,CAAA,CAAA;AAC7C,EAAA;AACF,CAAA;AAMO,IAAM,YAAA,GAAe;;;;;;;;AAS1B,EAAA,MAAA,EAAQ,CAAC,EAAA,KAAuB,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,KAAK,MAAM,CAAA;;;;;AAM7D,EAAA,SAAA,EAAW,CAAC,EAAA,KAAuB,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,QAAQ,MAAM,CAAA;;;;;;AAOnE,EAAA,cAAA,EAAgB,CAAC,EAAA,KAAgE;AAC/E,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,SAAS,MAAM,CAAA;AACnD,IAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AAE5C,IAAA,IAAI,cAAA,KAAmB,IAAI,OAAO,IAAA;AAElC,IAAA,MAAM,QAAA,GAAW,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAClD,IAAA,MAAM,UAAA,GAAa,SAAA,CAAU,KAAA,CAAM,cAAA,GAAiB,CAAC,CAAA;AAErD,IAAA,OAAO,EAAE,UAAU,UAAA,EAAW;AAChC,EAAA,CAAA;;;;;;;;AAUA,EAAA,YAAA,EAAc,CAAC,EAAA,KAAgE;AAC7E,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,SAAS,MAAM,CAAA;AACnD,IAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,SAAS,CAAA;AAElD,IAAA,IAAI,cAAA,KAAmB,IAAI,OAAO,IAAA;AAElC,IAAA,MAAM,QAAA,GAAW,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAClD,IAAA,MAAM,UAAA,GAAa,SAAA,CAAU,KAAA,CAAM,cAAA,GAAiB,CAAC,CAAA;AAErD,IAAA,OAAO,EAAE,UAAU,UAAA,EAAW;AAChC,EAAA,CAAA;;;;;;;AAQA,EAAA,SAAA,EAAW,CAAC,EAAA,KAAuD;AACjE,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,IAAI,MAAM,CAAA;AAC9C,IAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,SAAS,CAAA;AAElD,IAAA,IAAI,mBAAmB,EAAA,EAAI;AAEzB,MAAA,OAAO,EAAE,QAAQ,SAAA,EAAU;AAC7B,IAAA;AAEA,IAAA,MAAM,MAAA,GAAS,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAChD,IAAA,MAAM,EAAA,GAAK,SAAA,CAAU,KAAA,CAAM,cAAA,GAAiB,CAAC,CAAA;AAE7C,IAAA,OAAO,EAAE,QAAQ,EAAA,EAAG;AACtB,EAAA,CAAA;;;;;;;;;AAWA,EAAA,QAAA,EAAU,CAAC,EAAA,KAAuB,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,OAAO,MAAM,CAAA;;;;;;AAOjE,EAAA,kBAAA,EAAoB,CAAC,EAAA,KAAuB,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,KAAK,MAAM,CAAA;;;;;;AAOzE,EAAA,iBAAA,EAAmB,CAAC,EAAA,KAAuB,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,UAAU,MAAM,CAAA;;;;;;AAO7E,EAAA,cAAA,EAAgB,CAAC,EAAA,KAAuD;AACtE,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,SAAS,MAAM,CAAA;AACnD,IAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AAE5C,IAAA,IAAI,cAAA,KAAmB,IAAI,OAAO,IAAA;AAElC,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAC9C,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,cAAA,GAAiB,CAAC,CAAA;AAEhD,IAAA,OAAO,EAAE,MAAM,KAAA,EAAM;AACvB,EAAA,CAAA;;;;;;AAOA,EAAA,YAAA,EAAc,CAAC,EAAA,KAAuB,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,WAAW,MAAM,CAAA;;;;;;AAOzE,EAAA,wBAAA,EAA0B,CAAC,EAAA,KAAuB;AAChD,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,WAAW,MAAM,CAAA;AACrD,IAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AAE5C,IAAA,IAAI,cAAA,KAAmB,IAAI,OAAO,EAAA;AAElC,IAAA,OAAO,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAC1C,EAAA,CAAA;;;;;;AAOA,EAAA,eAAA,EAAiB,CAAC,EAAA,KAAuD;AACvE,IAAA,MAAM,SAAA,GAAY,EAAA,CAAG,KAAA,CAAM,QAAA,CAAS,SAAS,MAAM,CAAA;AACnD,IAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AAE5C,IAAA,IAAI,cAAA,KAAmB,IAAI,OAAO,IAAA;AAElC,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,cAAc,CAAA;AAC9C,IAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,KAAA,CAAM,cAAA,GAAiB,CAAC,CAAA;AAEhD,IAAA,OAAO,EAAE,MAAM,KAAA,EAAM;AACvB,EAAA;AAEF,CAAA;AA4BO,IAAM,OAAA,GAAU;;;;;;;;;;AAWrB,EAAA,iBAAA,EAAmB,CAAC,MAAA,KAAmB,CAAA,EAAG,QAAA,CAAS,IAAI,GAAG,MAAM,CAAA,CAAA;;;;;;EAOhE,iBAAA,EAAmB,CAAC,WAAmB,SAAA,KAAsB,CAAA,EAAG,SAAS,CAAA,EAAG,SAAS,GAAG,SAAS,CAAA,CAAA;;;;;;;;;AAWjG,EAAA,eAAA,EAAiB,CAAC,MAAA,KAAmB,CAAA,EAAG,QAAA,CAAS,IAAI,GAAG,MAAM,CAAA,CAAA;;;;;;EAO9D,eAAA,EAAiB,CAAC,WAAmB,SAAA,KAAsB,CAAA,EAAG,SAAS,CAAA,EAAG,SAAS,GAAG,SAAS,CAAA,CAAA;;;;;;;;;;AAY/F,EAAA,cAAA,EAAgB,CAAC,MAAA,KAAmB;AAElC,IAAA,MAAM,WAAA,GAAe,MAAA,KAAW,WAAA,CAAY,MAAA,GAAU,QAAA,GAAW,UAAA;AACjE,IAAA,OAAO,GAAG,YAAA,CAAa,IAAI,CAAA,EAAG,SAAS,GAAG,WAAW,CAAA,CAAA;AACvD,EAAA,CAAA;;;;;;;AAQA,EAAA,cAAA,EAAgB,CAAC,MAAA,KAAmB,MAAA;;;;;;;;;AAWpC,EAAA,gBAAA,EAAkB,CAAC,MAAA,KAAmB,CAAA,EAAG,QAAA,CAAS,IAAI,GAAG,MAAM,CAAA,CAAA;;;;;;EAO/D,gBAAA,EAAkB,CAAC,QAAA,EAAkB,UAAA,KACnC,CAAA,EAAG,QAAA,CAAS,QAAQ,CAAA,EAAG,QAAQ,CAAA,EAAG,SAAS,CAAA,EAAG,UAAU,CAAA;AAE5D,CAAA;AAKO,IAAM,SAAA,GAAY;EACvB,EAAA,EAAI,IAAA;EACJ,EAAA,EAAI,IAAA;EACJ,GAAA,EAAK,KAAA;EACL,iBAAA,EAAmB,iBAAA;;EACnB,iBAAA,EAAmB,iBAAA;;EACnB,eAAA,EAAiB,eAAA;;EACjB,eAAA,EAAiB,eAAA;;EACjB,cAAA,EAAgB,cAAA;;EAChB,cAAA,EAAgB,cAAA;;EAChB,gBAAA,EAAkB,gBAAA;;EAClB,gBAAA,EAAkB,gBAAA;;;EAGlB,OAAA,EAAS,QAAA;EACT,SAAA,EAAW,UAAA;EACX,QAAA,EAAU,UAAA;EACV,OAAA,EAAS,SAAA;EACT,gBAAA,EAAkB,iBAAA;;EAClB,KAAA,EAAO,OAAA;EACP,eAAA,EAAiB,gBAAA;EACjB,MAAA,EAAQ,QAAA;EACR,aAAA,EAAe,cAAA;;EACf,cAAA,EAAgB,eAAA;;EAChB,SAAA,EAAW,UAAA;EACX,QAAA,EAAU,UAAA;EACV,YAAA,EAAc,aAAA;EACd,QAAA,EAAU,UAAA;EACV,QAAA,EAAU,SAAA;EACV,UAAA,EAAY,WAAA;EACZ,UAAA,EAAY,WAAA;;EAGZ,QAAA,EAAU,SAAA;EACV,YAAA,EAAc,YAAA;EACd,UAAA,EAAY,WAAA;EACZ,SAAA,EAAW,UAAA;EACX,QAAA,EAAU,SAAA;EACV,YAAA,EAAc,aAAA;EACd,OAAA,EAAS,SAAA;EACT,IAAA,EAAM,MAAA;;EAGN,gBAAA,EAAkB,gBAAA;;EAClB,gBAAA,EAAkB,gBAAA;;EAClB,aAAA,EAAe,cAAA;EACf,WAAA,EAAa,YAAA;EACb,MAAA,EAAQ,QAAA;EACR,UAAA,EAAY,WAAA;;EAGZ,kBAAA,EAAoB,kBAAA;;EACpB,kBAAA,EAAoB,kBAAA;;EACpB,gBAAA,EAAkB;;;AAEpB,CAAA;AAQO,IAAM,OAAA,GAAU;EACrB,eAAA,EAAiB,oBAAA;EACjB,aAAA,EAAe,kBAAA;EACf,eAAA,EAAiB,mBAAA;EACjB,cAAA,EAAgB,mBAAA;EAChB,eAAA,EAAiB,oBAAA;;EACjB,gBAAA,EAAkB;;AACpB,CAAA;ACxnBO,SAAS,aAAa,MAAA,EAAsB;AACjD,EAAA,OAAO,IAAI,KAAK,MAAM,CAAA;AACxB;;;ACSO,IAAM,iCAAN,MAAuE;AAAA,EAG5E,WAAA,CACU,QACA,SAAA,EACR;AAFQ,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAER,IAAA,IAAA,CAAK,SAAA,GAAYA,kCAAA,CAAuB,IAAA,CAAK,MAAA,EAAQ;AAAA,MACnD,eAAA,EAAiB,EAAE,qBAAA,EAAuB,IAAA;AAAK,KAChD,CAAA;AAAA,EACH;AAAA,EATQ,SAAA;AAAA,EAWR,MAAM,GAAA,CACJ,QAAA,EACA,MAAA,EACsC;AACtC,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA;AAAA,QACpC,IAAIC,sBAAA,CAAW;AAAA,UACb,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,MAAM;AAAA;AACpD,SACD;AAAA,OACH;AAEA,MAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,QAAA,OAAOC,iBAAG,IAAI,CAAA;AAAA,MAChB;AAEA,MAAA,OAAOA,gBAAA,CAAG,IAAA,CAAK,eAAA,CAAgB,QAAA,CAAS,IAAI,CAAC,CAAA;AAAA,IAC/C,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,IAAA,EAAqD;AAChE,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AAErB,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIC,sBAAA,CAAW;AAAA,UACb,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,IAAA,EAAM;AAAA,YACJ,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,gBAAA,CAAiB,KAAK,QAAQ,CAAA;AAAA,YACzD,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,gBAAA,CAAiB,KAAK,MAAM,CAAA;AAAA,YACvD,YAAY,IAAA,CAAK,MAAA;AAAA,YACjB,OAAA,EAAS,IAAA,CAAK,SAAA,CAAU,IAAA,CAAK,OAAO,CAAA;AAAA,YACpC,QAAA,EAAU,KAAK,QAAA,IAAY,CAAA;AAAA,YAC3B,SAAA,EAAW,GAAA;AAAA,YACX,SAAA,EAAW;AAAA,WACb;AAAA,UACA,mBAAA,EAAqB;AAAA,SACtB;AAAA,OACH;AAEA,MAAA,OAAOH,gBAAA,EAAG;AAAA,IACZ,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAK,KAAA,CAA4B,SAAS,iCAAA,EAAmC;AAC3E,QAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,qBAAA,EAAuB,CAAA;AAAA,MACvD;AACA,MAAA,OAAOD,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,iBAAA,CACJ,QAAA,EACA,MAAA,EACA,OAAA,EACuB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,UAAA,GAAuB,CAAC,yBAAyB,CAAA;AACvD,MAAA,MAAM,KAAA,GAAgC,EAAE,YAAA,EAAc,WAAA,EAAY;AAClE,MAAA,MAAM,MAAA,GAAkC,EAAE,YAAA,EAAc,IAAA,CAAK,KAAI,EAAE;AAEnE,MAAA,IAAI,OAAA,CAAQ,YAAY,KAAA,CAAA,EAAW;AACjC,QAAA,UAAA,CAAW,KAAK,qBAAqB,CAAA;AACrC,QAAA,KAAA,CAAM,UAAU,CAAA,GAAI,SAAA;AACpB,QAAA,MAAA,CAAO,UAAU,CAAA,GAAI,IAAA,CAAK,SAAA,CAAU,QAAQ,OAAO,CAAA;AAAA,MACrD;AAEA,MAAA,IAAI,OAAA,CAAQ,aAAa,KAAA,CAAA,EAAW;AAClC,QAAA,UAAA,CAAW,KAAK,uBAAuB,CAAA;AACvC,QAAA,KAAA,CAAM,WAAW,CAAA,GAAI,UAAA;AACrB,QAAA,MAAA,CAAO,WAAW,IAAI,OAAA,CAAQ,QAAA;AAAA,MAChC;AAEA,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIE,yBAAA,CAAc;AAAA,UAChB,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,MAAM;AAAA,WACpD;AAAA,UACA,gBAAA,EAAkB,CAAA,IAAA,EAAO,UAAA,CAAW,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA;AAAA,UAC9C,wBAAA,EAA0B,KAAA;AAAA,UAC1B,yBAAA,EAA2B,MAAA;AAAA,UAC3B,mBAAA,EAAqB;AAAA,SACtB;AAAA,OACH;AAEA,MAAA,OAAOJ,gBAAA,EAAG;AAAA,IACZ,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAK,KAAA,CAA4B,SAAS,iCAAA,EAAmC;AAC3E,QAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,gBAAA,EAAkB,CAAA;AAAA,MAClD;AACA,MAAA,OAAOD,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,YAAA,CACJ,QAAA,EACA,MAAA,EACA,MAAA,EACuB;AACvB,IAAA,IAAI;AACF,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIE,yBAAA,CAAc;AAAA,UAChB,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,MAAM;AAAA,WACpD;AAAA,UACA,gBAAA,EAAkB,gDAAA;AAAA,UAClB,wBAAA,EAA0B;AAAA,YACxB,SAAA,EAAW,YAAA;AAAA,YACX,YAAA,EAAc;AAAA,WAChB;AAAA,UACA,yBAAA,EAA2B;AAAA,YACzB,SAAA,EAAW,MAAA;AAAA,YACX,YAAA,EAAc,KAAK,GAAA;AAAI,WACzB;AAAA,UACA,mBAAA,EAAqB;AAAA,SACtB;AAAA,OACH;AAEA,MAAA,OAAOJ,gBAAA,EAAG;AAAA,IACZ,SAAS,KAAA,EAAgB;AACvB,MAAA,IAAK,KAAA,CAA4B,SAAS,iCAAA,EAAmC;AAC3E,QAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,gBAAA,EAAkB,CAAA;AAAA,MAClD;AACA,MAAA,OAAOD,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,MAAA,CAAO,QAAA,EAAoB,MAAA,EAAuC;AACtE,IAAA,IAAI;AACF,MAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,QACnB,IAAIG,yBAAA,CAAc;AAAA,UAChB,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,GAAA,EAAK;AAAA,YACH,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,QAAQ,CAAA;AAAA,YACpD,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,iBAAiB,MAAM;AAAA;AACpD,SACD;AAAA,OACH;AAEA,MAAA,OAAOL,gBAAA,EAAG;AAAA,IACZ,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAA,CAAIC,sCAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEQ,gBAAgB,IAAA,EAA6C;AACnE,IAAA,MAAM,WAAW,YAAA,CAAa,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,EAAE,CAAW,CAAA;AACnE,IAAA,MAAM,SAAS,YAAA,CAAa,kBAAA,CAAmB,IAAA,CAAK,SAAA,CAAU,EAAE,CAAW,CAAA;AAE3E,IAAA,OAAO;AAAA,MACL,QAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA,EAAQ,KAAK,YAAY,CAAA;AAAA,MACzB,OAAA,EAAS,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,SAAS,CAAW,CAAA;AAAA,MAC7C,QAAA,EAAW,IAAA,CAAK,UAAU,CAAA,IAAgB,CAAA;AAAA,MAC1C,SAAA,EAAW,YAAA,CAAa,IAAA,CAAK,WAAW,CAAW,CAAA;AAAA,MACnD,SAAA,EAAW,YAAA,CAAa,IAAA,CAAK,WAAW,CAAW;AAAA,KACrD;AAAA,EACF;AACF;ACjLO,IAAM,4BAAN,MAA6D;AAAA,EAGlE,WAAA,CACU,QACA,SAAA,EACR;AAFQ,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AAER,IAAA,IAAA,CAAK,SAAA,GAAYJ,kCAAAA,CAAuB,IAAA,CAAK,MAAM,CAAA;AAAA,EACrD;AAAA,EAPQ,SAAA;AAAA,EASR,MAAM,kBAAA,CACJ,MAAA,EACA,OAAA,EACuC;AACvC,IAAA,IAAI;AACF,MAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,OAAA,EAAS,KAAA,IAAS,IAAI,GAAG,CAAA;AAChD,MAAA,IAAI,iBAAA;AAEJ,MAAA,IAAI,SAAS,MAAA,EAAQ;AACnB,QAAA,IAAI;AACF,UAAA,iBAAA,GAAoB,IAAA,CAAK,KAAA;AAAA,YACvB,OAAO,IAAA,CAAK,OAAA,CAAQ,QAAQ,WAAW,CAAA,CAAE,SAAS,OAAO;AAAA,WAC3D;AAAA,QACF,CAAA,CAAA,MAAQ;AACN,UAAA,OAAOG,iBAAAA,CAAIC,sCAAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,QAChD;AAAA,MACF;AAEA,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,SAAA,CAAU,IAAA;AAAA,QACpC,IAAII,wBAAA,CAAa;AAAA,UACf,WAAW,IAAA,CAAK,SAAA;AAAA,UAChB,WAAW,OAAA,CAAQ,eAAA;AAAA,UACnB,sBAAA,EAAwB,CAAA,EAAG,SAAA,CAAU,iBAAiB,CAAA,YAAA,CAAA;AAAA,UACtD,yBAAA,EAA2B;AAAA,YACzB,WAAA,EAAa,OAAA,CAAQ,iBAAA,CAAkB,MAAM;AAAA,WAC/C;AAAA,UACA,oBAAA,EAAsB,CAAA,EAAG,SAAA,CAAU,EAAE,CAAA,EAAA,EAAK,SAAA,CAAU,SAAS,CAAA,EAAA,EAAK,SAAA,CAAU,QAAQ,CAAA,EAAA,EAAK,SAAA,CAAU,OAAO,CAAA,CAAA;AAAA,UAC1G,KAAA,EAAO,KAAA;AAAA,UACP,iBAAA,EAAmB,iBAAA;AAAA,UACnB,gBAAA,EAAkB;AAAA,SACnB;AAAA,OACH;AAEA,MAAA,MAAM,YAA2B,QAAA,CAAS,KAAA,IAAS,EAAC,EAAG,GAAA,CAAI,CAAC,IAAA,MAAU;AAAA,QACpE,IAAI,YAAA,CAAa,SAAA,CAAU,IAAA,CAAK,SAAA,CAAU,EAAE,CAAW,CAAA;AAAA,QACvD,MAAA,EAAQ,IAAA,CAAK,SAAA,CAAU,OAAO,CAAA;AAAA,QAC9B,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,SAAS,CAAA;AAAA,QAClC,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,QAAQ;AAAA,OACnC,CAAE,CAAA;AAEF,MAAA,MAAM,OAAA,GAAU,CAAC,CAAC,QAAA,CAAS,gBAAA;AAC3B,MAAA,MAAM,MAAA,GAAS,OAAA,GACX,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,SAAA,CAAU,QAAA,CAAS,gBAAgB,CAAC,CAAA,CAAE,QAAA,CAAS,WAAW,CAAA,GAC3E,IAAA;AAEJ,MAAA,MAAM,UAAA,GAA+B;AAAA,QACnC,IAAA,EAAM,QAAA;AAAA,QACN,KAAA;AAAA,QACA,OAAO,QAAA,CAAS,MAAA;AAAA,QAChB,OAAA;AAAA,QACA,OAAA,EAAS,CAAC,CAAC,OAAA,EAAS,MAAA;AAAA,QACpB;AAAA,OACF;AAEA,MAAA,OAAON,gBAAAA,CAAG,QAAA,EAAU,EAAE,UAAA,EAAY,CAAA;AAAA,IACpC,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAAA,CAAIC,sCAAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,UAAA,EAAkD;AAClE,IAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC3B,MAAA,OAAOF,iBAAG,CAAC,CAAA;AAAA,IACb;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,MAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,MAAA,MAAM,SAAA,GAAY,EAAA;AAElB,MAAA,KAAA,IAAS,IAAI,CAAA,EAAG,CAAA,GAAI,UAAA,CAAW,MAAA,EAAQ,KAAK,SAAA,EAAW;AACrD,QAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,KAAA,CAAM,CAAA,EAAG,IAAI,SAAS,CAAA;AAE/C,QAAA,MAAM,aAAA,GAAgB,KAAA,CAAM,GAAA,CAAI,CAAC,SAAA,MAAe;AAAA,UAC9C,MAAA,EAAQ;AAAA,YACN,WAAW,IAAA,CAAK,SAAA;AAAA,YAChB,GAAA,EAAK;AAAA,cACH,CAAC,SAAA,CAAU,EAAE,GAAG,UAAA,CAAW,WAAW,SAAS,CAAA;AAAA,cAC/C,CAAC,SAAA,CAAU,EAAE,GAAG,WAAW,UAAA;AAAW,aACxC;AAAA,YACA,gBAAA,EAAkB,CAAA,uDAAA,CAAA;AAAA,YAClB,wBAAA,EAA0B;AAAA,cACxB,cAAc,SAAA,CAAU,UAAA;AAAA,cACxB,aAAa,SAAA,CAAU,iBAAA;AAAA,cACvB,aAAa,SAAA,CAAU;AAAA,aACzB;AAAA,YACA,yBAAA,EAA2B;AAAA,cACzB,YAAA,EAAc;AAAA,aAChB;AAAA,YACA,mBAAA,EAAqB,CAAA,iBAAA,EAAoB,SAAA,CAAU,EAAE,CAAA,sCAAA;AAAA;AACvD,SACF,CAAE,CAAA;AAEF,QAAA,IAAI;AACF,UAAA,MAAM,KAAK,SAAA,CAAU,IAAA;AAAA,YACnB,IAAIO,gCAAA,CAAqB,EAAE,aAAA,EAAe,eAAe;AAAA,WAC3D;AACA,UAAA,KAAA,IAAS,KAAA,CAAM,MAAA;AAAA,QACjB,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF;AAEA,MAAA,OAAOP,iBAAG,KAAK,CAAA;AAAA,IACjB,CAAA,CAAA,MAAQ;AACN,MAAA,OAAOC,iBAAAA,CAAIC,sCAAAA,CAAmB,cAAA,EAAgB,CAAA;AAAA,IAChD;AAAA,EACF;AACF;;;ACrHO,SAAS,iCACd,MAAA,EACyB;AACzB,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA,IAAU,IAAIM,6BAAAA,CAAe,EAAE,CAAA;AAErD,EAAA,OAAO;AAAA,IACL,sBAAA,EAAwB,IAAI,8BAAA,CAA+B,MAAA,EAAQ,OAAO,SAAS,CAAA;AAAA,IACnF,iBAAA,EAAmB,IAAI,yBAAA,CAA0B,MAAA,EAAQ,OAAO,SAAS;AAAA,GAC3E;AACF","file":"index.cjs","sourcesContent":["import type { UserId } from '../types/branded';\n\n/**\n * User Status\n *\n * Global account status (not tenant-specific).\n *\n * - 'active': Normal active account\n * - 'suspended': Temporarily suspended (security, payment, etc.)\n * - 'deleted': Soft deleted account\n */\nexport type UserStatus = 'active' | 'suspended' | 'deleted';\n\n/**\n * User Status Constants\n *\n * Type-safe constants for UserStatus values.\n */\nexport const USER_STATUS = {\n ACTIVE: 'active' as const,\n SUSPENDED: 'suspended' as const,\n DELETED: 'deleted' as const,\n} satisfies Record<string, UserStatus>;\n\n/**\n * MFA Method\n *\n * Multi-factor authentication methods supported by the system.\n *\n * **Classification by Server Delivery:**\n *\n * **Server Delivery Required (requiresServerDelivery = true):**\n * - 'sms': Server sends OTP code via SMS\n * - 'email': Server sends OTP code via email\n *\n * **User Already Has (requiresServerDelivery = false):**\n * - 'totp': User generates code from authenticator app\n * - 'backup_codes': User has pre-generated recovery codes\n * - 'hardware': User has physical security key\n *\n * **Plugin Metadata:**\n * Plugins should declare `requiresServerDelivery` for each method they support.\n */\nexport type MFAMethod = 'totp' | 'sms' | 'email' | 'backup_codes' | 'hardware';\n\n/**\n * MFA Method Constants\n *\n * Type-safe constants for MFA methods.\n * Prevents magic strings in code.\n *\n * @example\n * ```typescript\n * // ✅ Good - Use constants\n * await findByUserIdAndMethod(userId, MFA_METHOD.TOTP);\n *\n * // ❌ Bad - Magic string\n * await findByUserIdAndMethod(userId, 'totp');\n * ```\n */\nexport const MFA_METHOD = {\n TOTP: 'totp' as const,\n SMS: 'sms' as const,\n EMAIL: 'email' as const,\n BACKUP_CODES: 'backup_codes' as const,\n BACKUP: 'backup' as const, // Shorthand used in MFA credentials\n HARDWARE: 'hardware' as const,\n} as const;\n\n/**\n * UserAuth Entity (V2 - Plugin-First Architecture)\n *\n * **Design Principle:** UserAuth = \"CAN THEY login to the system (globally)\"\n *\n * Purpose:\n * - Global authentication state (not tenant-specific)\n * - MFA enforcement and configuration\n * - Account-level security controls\n *\n * Key Features:\n * - Checked AFTER credential verification (optimization)\n * - Single record per user (1:1 with User entity)\n * - Global scope (tenant-specific access → TenantMember)\n * - Token revocation via tokenVersion\n *\n * When to Check:\n * 1. Identity lookup (fast)\n * 2. Guard check (fast, per-method)\n * 3. Credential verification (potentially slow)\n * 4. **→ UserAuth check (AFTER credential verified)** ← YOU ARE HERE\n * 5. MFA challenge (if required)\n * 6. Session creation\n *\n * Why Check Late?\n * - Avoid DB load for invalid credentials (80%+ of failed logins)\n * - Guards already provide brute force protection\n * - UserAuth check only needed for valid credentials\n *\n * ⚠️ WARNING - UserAuth vs TenantMember:\n *\n * **UserAuth (GLOBAL):**\n * - Can user login to the system at all?\n * - Global MFA enforcement\n * - Account-level ban/suspension\n * - Token revocation (all tenants)\n *\n * **TenantMember (TENANT-SPECIFIC):**\n * - Can user access THIS specific tenant?\n * - Tenant-level suspension\n * - Role/permission assignment\n * - Audience-aware access control\n *\n * Example Flow:\n * ```\n * UserAuth check: userStatus = 'active' ✅\n * ↓\n * TenantMember check (tenant A): status = 'suspended' ❌\n * → User CAN login but NOT access tenant A\n *\n * UserAuth check: userStatus = 'banned' ❌\n * → User CANNOT login anywhere (global block)\n * ```\n *\n * @example\n * // Active user with TOTP MFA\n * {\n * userId: 'usr_abc123',\n * userStatus: 'active',\n * mfaRequired: true,\n * mfaMethods: ['totp']\n * }\n * // TOTP secret stored separately as Credential:\n * // { type: 'totp', metadata: { secret: 'encrypted...', algorithm: 'sha1' } }\n *\n * @example\n * // Banned user (cannot login anywhere)\n * {\n * userId: 'usr_xyz789',\n * userStatus: 'banned',\n * mfaRequired: false,\n * mfaMethods: null\n * }\n *\n * @see {@link https://github.com/your-repo/auth-craft/blob/main/ARCHITECTURE_V2.md ARCHITECTURE_V2.md}\n */\nexport interface UserAuth {\n /**\n * User ID (primary key)\n *\n * 1:1 relationship with User entity.\n * Created automatically when user is registered.\n */\n userId: UserId;\n\n /**\n * Global user status\n *\n * Controls whether user can login to the system at all.\n *\n * - 'active': ✅ Can login (subject to other checks)\n * - 'suspended': ❌ Temporarily blocked (security, payment, etc.)\n * - 'deleted': ❌ Soft deleted account\n *\n * Checked AFTER credential verification to avoid unnecessary DB load.\n *\n * Difference from TenantMember.status:\n * - UserAuth.userStatus: Global (all tenants)\n * - TenantMember.status: Per-tenant (specific workspace/org)\n */\n userStatus: UserStatus;\n\n /**\n * Whether MFA is required for this user\n *\n * When true, user must complete MFA challenge after credential verification.\n *\n * Enforcement levels:\n * 1. **Policy-level** (policy.mfaEnforced): All users must use MFA\n * 2. **User-level** (UserAuth.mfaRequired): This specific user requires MFA\n * 3. **Tenant-level** (TenantMember policy): Tenant enforces MFA for admins\n *\n * Order of checks:\n * - If policy.mfaEnforced → MFA required regardless of user setting\n * - Else if UserAuth.mfaRequired → MFA required for this user\n * - Else if tenant policy → MFA required for this tenant/role\n *\n * Default: false\n */\n mfaRequired: boolean;\n\n /**\n * Available MFA methods for this user\n *\n * Array of MFA methods user has configured.\n * null = no MFA methods configured (mfaRequired should be false)\n *\n * Examples:\n * - ['totp'] - TOTP only\n * - ['totp', 'sms'] - TOTP preferred, SMS backup\n * - ['totp', 'backup_codes'] - TOTP + recovery codes\n * - null - No MFA configured\n *\n * **Architecture Note:**\n * UserAuth only tracks WHICH methods are available (metadata).\n * Actual MFA secrets/data are stored as Credentials:\n * - TOTP secret → Credential { type: 'totp', metadata: { secret, algorithm } }\n * - Backup codes → Credential { type: 'backup_codes', metadata: { codes, usedCodes } }\n * - SMS/Email → Use identity value (no credential needed)\n *\n * When MFA challenge is issued, user can choose from these methods.\n */\n mfaMethods: MFAMethod[] | null;\n\n /**\n * When this record was created\n */\n createdAt: Date;\n\n /**\n * When this record was last updated\n */\n updatedAt: Date;\n}\n","/**\r\n * DynamoDB Key Management\r\n * Centralized key construction, extraction, and validation\r\n *\r\n * Best practices:\r\n * - PK prefixes: 3-5 characters\r\n * - SK prefixes: 1-3 characters\r\n * - Use slice() for extraction (faster than split/regex)\r\n * - Document what each key stores\r\n */\r\n\r\nimport { USER_STATUS } from '@auth-craft/auth-core/entities';\r\n\r\n/**\r\n * Entity Type Prefixes (without delimiter)\r\n * Used for GSI key patterns like USR#ACTIVE\r\n */\r\nexport const EntityPrefix = {\r\n USER: 'USR',\r\n} as const;\r\n\r\n/**\r\n * Partition Key (PK) Prefixes\r\n * Length: 3-5 characters\r\n */\r\nexport const PkPrefix = {\r\n USER: `${EntityPrefix.USER}#` as const,\r\n SESSION: 'SES#',\r\n PROVIDER: 'PRV#',\r\n CHALLENGE: 'CHL#',\r\n IDENTITY: 'IDT#', // Identity (email/phone/username)\r\n CREDENTIAL: 'CRD#', // Credential (password, passkey, etc.)\r\n // Note: MFA credentials use PK: USR#{userId} (stored under user, not separate partition)\r\n TENANT: 'TNT#', // TenantMember (SCHEMA_V4 multi-tenant)\r\n} as const;\r\n\r\n/**\r\n * Sort Key (SK) Prefixes\r\n * Length: 1-3 characters\r\n */\r\nexport const SkPrefix = {\r\n METADATA: 'MD', // Metadata suffix for entities\r\n AUTH: 'AUTH', // UserAuth entity (SCHEMA_V4)\r\n PROFILE: 'PROF', // UserProfile entity (SCHEMA_V4)\r\n PROVIDER: 'PRV#',\r\n MFA: 'MFA#', // UserMFAAuthenticator entity (SCHEMA_V4)\r\n} as const;\r\n\r\n/**\r\n * Key Delimiter\r\n * Used to separate compound key parts\r\n */\r\nconst DELIMITER = '#';\r\n\r\n/**\r\n * Key Pattern Builders\r\n * Functions to construct partition and sort keys\r\n */\r\nexport const KeyPattern = {\r\n // ============================================\r\n // USER\r\n // ============================================\r\n\r\n /**\r\n * User PK\r\n * Pattern: USR#{userId}\r\n */\r\n USER_PK: (userId: string) => `${PkPrefix.USER}${userId}`,\r\n\r\n /**\r\n * User Auth SK (SCHEMA_V4)\r\n * Pattern: AUTH\r\n * Stores: userStatus, mfaRequired, mfaMethods[], createdAt, updatedAt\r\n *\r\n * Removed in SCHEMA_V4:\r\n * - email, phone fields → moved to Identity entity\r\n * - passwordHash → moved to Credential entity\r\n * - lockedUntil, failedLoginAttempts → moved to Identity.guards (per-method)\r\n * - MFA credentials (mfaSecret, mfaBackupCodes) → moved to UserMFAAuthenticator items\r\n * - Profile fields (givenName, familyName, avatarUrl, locale, metadata) → moved to PROF item\r\n * - tokenVersion → removed (use Session.revokedAt instead)\r\n * - permissions → removed from UserAuth (use TenantMember.permMask or Session.permMask)\r\n */\r\n USER_AUTH_SK: () => SkPrefix.AUTH,\r\n\r\n /**\r\n * User Profile SK (SCHEMA_V4)\r\n * Pattern: PROF\r\n * Stores: userStatus, deletedAt, givenName, familyName, avatarUrl,\r\n * locale, metadata, createdAt, updatedAt, statusPk, statusSk\r\n *\r\n * Removed in SCHEMA_V4:\r\n * - email, phone fields → moved to Identity entity\r\n */\r\n USER_PROFILE_SK: () => SkPrefix.PROFILE,\r\n\r\n // ============================================\r\n // USER MFA AUTHENTICATOR (SCHEMA_V4)\r\n // ============================================\r\n\r\n /**\r\n * UserMFAAuthenticator SK (single-instance methods like TOTP)\r\n * Pattern: MFA#{method}\r\n * Example: MFA#totp\r\n * Used by: Single-instance MFA methods that don't need ID\r\n */\r\n USER_MFA_SK: (method: string) => `${SkPrefix.MFA}${method}`,\r\n\r\n /**\r\n * UserMFAAuthenticator SK (multi-instance methods like WebAuthn)\r\n * Pattern: MFA#{method}#{id}\r\n * Example: MFA#webauthn#key_abc123, MFA#passkey#pk_xyz789\r\n * Used by: Multi-instance MFA methods (security keys, passkeys)\r\n */\r\n USER_MFA_WITH_ID_SK: (method: string, id: string) =>\r\n `${SkPrefix.MFA}${method}${DELIMITER}${id}`,\r\n\r\n // ============================================\r\n // TENANT MEMBER (SCHEMA_V4)\r\n // ============================================\r\n\r\n /**\r\n * Tenant Member PK\r\n * Pattern: TNT#{tenantId}\r\n * Example: TNT#org-abc\r\n * Used by: DynamoDBTenantMemberItem (main table)\r\n */\r\n TENANT_MEMBER_PK: (tenantId: string) => `${PkPrefix.TENANT}${tenantId}`,\r\n\r\n /**\r\n * Tenant Member SK\r\n * Pattern: USR#{userId}\r\n * Example: USR#01HQZX8V9ABCDEFGHIJK\r\n * Used by: DynamoDBTenantMemberItem (main table)\r\n */\r\n TENANT_MEMBER_SK: (userId: string) => `${PkPrefix.USER}${userId}`,\r\n\r\n // ============================================\r\n // SESSION\r\n // ============================================\r\n\r\n /**\r\n * Session PK (New session-centric design)\r\n * Pattern: SES#{sessionId}\r\n * Example: SES#01HQZX8V9ABCDEFGHIJK\r\n */\r\n SESSION_PK: (sessionId: string) => `${PkPrefix.SESSION}${sessionId}`,\r\n\r\n /**\r\n * Session Metadata SK\r\n * Pattern: MD\r\n * Stores: userId, tokenId, tenantId, audience, permMask, roleIds,\r\n * deviceLabel, country, city, createdAt, expiresAt, lastUsedAt,\r\n * revokedAt, metadata, TTL\r\n * Note: sessionId extracted from PK\r\n */\r\n SESSION_METADATA_SK: () => SkPrefix.METADATA,\r\n\r\n /**\r\n * Session SK (alias for SESSION_METADATA_SK)\r\n */\r\n SESSION_SK: () => SkPrefix.METADATA,\r\n\r\n /**\r\n * Provider SK\r\n * Pattern: PRV#{provider}#{externalId}\r\n * Used for:\r\n * - GSI_UserProviders sort key (userProviderSk)\r\n * - DynamoDBProviderRegistryItem PK\r\n */\r\n PROVIDER_SK: (provider: string, externalId: string) =>\r\n `${SkPrefix.PROVIDER}${provider}${DELIMITER}${externalId}`,\r\n\r\n // ============================================\r\n // PROVIDER (Provider-centric design)\r\n // ============================================\r\n\r\n /**\r\n * Provider PK (Main table)\r\n * Pattern: PRV#{provider}#{externalId}\r\n * Example: PRV#google#123456789\r\n * Used by: DynamoDBUserProviderItem (main table)\r\n */\r\n PROVIDER_PK: (provider: string, externalId: string) =>\r\n `${PkPrefix.PROVIDER}${provider}${DELIMITER}${externalId}`,\r\n\r\n /**\r\n * Provider Metadata SK\r\n * Pattern: MD\r\n * Stores: userId, system, externalUsername, externalEmail, externalPhone,\r\n * externalName, externalAvatarUrl, accessToken, refreshToken,\r\n * tokenExpiresAt, createdAt, metadata\r\n * Note: id generated at mapper layer from PK (base64url), NOT stored\r\n * Note: provider and externalId extracted from PK\r\n */\r\n PROVIDER_METADATA_SK: () => SkPrefix.METADATA,\r\n\r\n // ============================================\r\n // CHALLENGE\r\n // ============================================\r\n\r\n /**\r\n * Challenge PK\r\n * Pattern: CHL#{challengeId}\r\n * Example: CHL#abc123xyz\r\n */\r\n CHALLENGE_PK: (challengeId: string) => `${PkPrefix.CHALLENGE}${challengeId}`,\r\n\r\n /**\r\n * Challenge Metadata SK\r\n * Pattern: MD\r\n * Stores: userId, tenantId, audience, purpose, status, phase, allowedMethods,\r\n * method, codeHash, attempts, maxAttempts, metadata, flowKey,\r\n * expiresAt, createdAt, TTL\r\n * Note: challengeId extracted from PK\r\n */\r\n CHALLENGE_SK: () => SkPrefix.METADATA,\r\n\r\n // ============================================\r\n // IDENTITY\r\n // ============================================\r\n\r\n /**\r\n * Identity PK\r\n * Pattern: IDT#{type}#{value}\r\n * Examples:\r\n * - IDT#email#user@example.com\r\n * - IDT#phone#+84987654321\r\n * - IDT#username#johndoe\r\n *\r\n * Purpose: Fast O(1) lookup by identity type and value\r\n */\r\n IDENTITY_PK: (type: string, value: string): string => {\r\n // Note: value should already be normalized by the caller (plugin/usecase layer)\r\n return `${PkPrefix.IDENTITY}${type}#${value}`;\r\n },\r\n\r\n /**\r\n * Identity Metadata SK\r\n * Pattern: MD\r\n * Stores: userId, loginEnabled, verifiedAt, guards, createdAt, updatedAt\r\n * Note: type and value extracted from PK\r\n */\r\n IDENTITY_SK: () => SkPrefix.METADATA,\r\n\r\n // ============================================\r\n // CREDENTIAL\r\n // ============================================\r\n\r\n /**\r\n * Credential PK\r\n * Pattern: CRD#{credentialId}\r\n * Example: CRD#cred_01JCQR8XMZP2KGH7NWV8F9E3TS\r\n */\r\n CREDENTIAL_PK: (credentialId: string) => `${PkPrefix.CREDENTIAL}${credentialId}`,\r\n\r\n /**\r\n * Credential Metadata SK\r\n * Pattern: MD\r\n * Stores: userId, value, metadata, isActive, createdAt, updatedAt, lastUsedAt\r\n * Note: credentialId and type extracted from PK and GSI SK\r\n */\r\n CREDENTIAL_SK: () => SkPrefix.METADATA,\r\n\r\n /**\r\n * Credential Type Prefix (for GSI query)\r\n * Pattern: CRD#{type}#\r\n * Used to query credentials by type with begins_with\r\n */\r\n CREDENTIAL_TYPE_PREFIX: (type: string) => `${PkPrefix.CREDENTIAL}${type}#`,\r\n\r\n /**\r\n * Credential User GSI SK\r\n * Pattern: CRD#{type}#{credentialId}\r\n * Example: CRD#password#cred_01JCQR8XMZP2KGH7NWV8F9E3TS\r\n * Allows querying credentials by user and filtering by type\r\n */\r\n CREDENTIAL_USER_SK: (type: string, credentialId: string) =>\r\n `${PkPrefix.CREDENTIAL}${type}#${credentialId}`,\r\n\r\n // ============================================\r\n // IDENTITY\r\n // ============================================\r\n\r\n /**\r\n * User Index GSI PK (for Identity)\r\n * Pattern: USR#{userId}\r\n * Example: USR#01JCQR8XMZP2KGH7NWV8F9E3TS\r\n *\r\n * Purpose: Query all identities for a specific user\r\n */\r\n USER_INDEX_PK: (userId: string): string => `${PkPrefix.USER}${userId}`,\r\n\r\n /**\r\n * User Index GSI SK (for Identity)\r\n * Pattern: IDT#{type}#{value}\r\n * Examples:\r\n * - IDT#email#user@example.com\r\n * - IDT#phone#+84987654321\r\n *\r\n * Purpose: Sort identities by type and extract type/value without additional attributes\r\n */\r\n USER_INDEX_SK: (type: string, value: string): string => {\r\n // Note: value should already be normalized by the caller (plugin/usecase layer)\r\n return `${PkPrefix.IDENTITY}${type}#${value}`;\r\n },\r\n} as const;\r\n\r\n/**\r\n * Key Extractors\r\n * Fast ID extraction using slice() - avoids regex/split for performance\r\n */\r\nexport const KeyExtractor = {\r\n // ============================================\r\n // PK Extractors\r\n // ============================================\r\n\r\n /**\r\n * Extract userId from User PK\r\n * Pattern: USR#{userId} -> userId\r\n */\r\n userId: (pk: string): string => pk.slice(PkPrefix.USER.length),\r\n\r\n /**\r\n * Extract sessionId from Session PK\r\n * Pattern: SES#{sessionId} -> sessionId\r\n */\r\n sessionId: (pk: string): string => pk.slice(PkPrefix.SESSION.length),\r\n\r\n /**\r\n * Extract provider and externalId from Provider PK\r\n * Pattern: PRV#{provider}#{externalId} -> { provider, externalId }\r\n * Example: PRV#google#123456789 -> { provider: 'google', externalId: '123456789' }\r\n */\r\n providerPKInfo: (pk: string): { provider: string; externalId: string } | null => {\r\n const remainder = pk.slice(PkPrefix.PROVIDER.length);\r\n const delimiterIndex = remainder.indexOf('#');\r\n\r\n if (delimiterIndex === -1) return null;\r\n\r\n const provider = remainder.slice(0, delimiterIndex);\r\n const externalId = remainder.slice(delimiterIndex + 1);\r\n\r\n return { provider, externalId };\r\n },\r\n\r\n // ============================================\r\n // SK Extractors\r\n // ============================================\r\n\r\n /**\r\n * Extract provider and externalId from Provider SK\r\n * Pattern: PRV#{provider}#{externalId} -> { provider, externalId }\r\n */\r\n providerInfo: (sk: string): { provider: string; externalId: string } | null => {\r\n const remainder = sk.slice(SkPrefix.PROVIDER.length);\r\n const delimiterIndex = remainder.indexOf(DELIMITER);\r\n\r\n if (delimiterIndex === -1) return null;\r\n\r\n const provider = remainder.slice(0, delimiterIndex);\r\n const externalId = remainder.slice(delimiterIndex + 1);\r\n\r\n return { provider, externalId };\r\n },\r\n\r\n /**\r\n * Extract method and optional id from MFA SK\r\n * Pattern: MFA#{method} or MFA#{method}#{id} -> { method, id? }\r\n * Example: MFA#totp -> { method: 'totp' }\r\n * Example: MFA#webauthn#key_abc -> { method: 'webauthn', id: 'key_abc' }\r\n */\r\n mfaSKInfo: (sk: string): { method: string; id?: string } | null => {\r\n const remainder = sk.slice(SkPrefix.MFA.length);\r\n const delimiterIndex = remainder.indexOf(DELIMITER);\r\n\r\n if (delimiterIndex === -1) {\r\n // Single-instance method (no id)\r\n return { method: remainder };\r\n }\r\n\r\n const method = remainder.slice(0, delimiterIndex);\r\n const id = remainder.slice(delimiterIndex + 1);\r\n\r\n return { method, id };\r\n },\r\n\r\n // ============================================\r\n // Tenant Extractors (SCHEMA_V4)\r\n // ============================================\r\n\r\n /**\r\n * Extract tenantId from TenantMember PK\r\n * Pattern: TNT#{tenantId} -> tenantId\r\n * Example: TNT#org-abc -> org-abc\r\n */\r\n tenantId: (pk: string): string => pk.slice(PkPrefix.TENANT.length),\r\n\r\n /**\r\n * Extract userId from TenantMember SK\r\n * Pattern: USR#{userId} -> userId\r\n * Example: USR#01HQZX8V9ABCDEFGHIJK -> 01HQZX8V9ABCDEFGHIJK\r\n */\r\n tenantMemberUserId: (sk: string): string => sk.slice(PkPrefix.USER.length),\r\n\r\n /**\r\n * Extract challengeId from Challenge PK\r\n * Pattern: CHL#{challengeId} -> challengeId\r\n * Example: CHL#abc123xyz -> abc123xyz\r\n */\r\n challengeIdFromPK: (pk: string): string => pk.slice(PkPrefix.CHALLENGE.length),\r\n\r\n /**\r\n * Extract type and value from Identity PK\r\n * Pattern: IDT#{type}#{value} -> { type, value }\r\n * Example: IDT#email#user@example.com -> { type: 'email', value: 'user@example.com' }\r\n */\r\n identityPKInfo: (pk: string): { type: string; value: string } | null => {\r\n const remainder = pk.slice(PkPrefix.IDENTITY.length);\r\n const delimiterIndex = remainder.indexOf('#');\r\n\r\n if (delimiterIndex === -1) return null;\r\n\r\n const type = remainder.slice(0, delimiterIndex);\r\n const value = remainder.slice(delimiterIndex + 1);\r\n\r\n return { type, value };\r\n },\r\n\r\n /**\r\n * Extract credentialId from Credential PK\r\n * Pattern: CRD#{credentialId} -> credentialId\r\n * Example: CRD#cred_01JCQR8XMZP2KGH7NWV8F9E3TS -> cred_01JCQR8XMZP2KGH7NWV8F9E3TS\r\n */\r\n credentialId: (pk: string): string => pk.slice(PkPrefix.CREDENTIAL.length),\r\n\r\n /**\r\n * Extract type from Credential User GSI SK\r\n * Pattern: CRD#{type}#{credentialId} -> type\r\n * Example: CRD#password#cred_abc -> password\r\n */\r\n credentialTypeFromUserSK: (sk: string): string => {\r\n const remainder = sk.slice(PkPrefix.CREDENTIAL.length);\r\n const delimiterIndex = remainder.indexOf('#');\r\n\r\n if (delimiterIndex === -1) return '';\r\n\r\n return remainder.slice(0, delimiterIndex);\r\n },\r\n\r\n /**\r\n * Extract type and value from User Index SK (GSI)\r\n * Pattern: IDT#{type}#{value} -> { type, value }\r\n * Example: IDT#email#user@example.com -> { type: 'email', value: 'user@example.com' }\r\n */\r\n userIndexSKInfo: (sk: string): { type: string; value: string } | null => {\r\n const remainder = sk.slice(PkPrefix.IDENTITY.length);\r\n const delimiterIndex = remainder.indexOf('#');\r\n\r\n if (delimiterIndex === -1) return null;\r\n\r\n const type = remainder.slice(0, delimiterIndex);\r\n const value = remainder.slice(delimiterIndex + 1);\r\n\r\n return { type, value };\r\n },\r\n\r\n} as const;\r\n\r\n/**\r\n * Query Helpers\r\n * Prefix patterns for DynamoDB queries\r\n */\r\nexport const QueryPrefix = {\r\n /**\r\n * Get Provider SK prefix for queries\r\n * Use with: SK begins_with\r\n * @param provider - Optional provider name to filter specific provider\r\n */\r\n PROVIDER: (provider?: string) =>\r\n provider ? `${SkPrefix.PROVIDER}${provider}${DELIMITER}` : SkPrefix.PROVIDER,\r\n\r\n /**\r\n * Get MFA SK prefix for queries\r\n * Use with: SK begins_with\r\n * @param method - Optional MFA method to filter specific method\r\n */\r\n MFA: (method?: string) =>\r\n method ? `${SkPrefix.MFA}${method}${DELIMITER}` : SkPrefix.MFA,\r\n} as const;\r\n\r\n/**\r\n * GSI Key Builders\r\n * Helper functions to build GSI keys for queries\r\n */\r\nexport const GSIKeys = {\r\n // ============================================\r\n // GSI_ActiveSessions\r\n // ============================================\r\n\r\n /**\r\n * Active Sessions GSI PK\r\n * Pattern: USR#{userId} (user-partitioned for scalability)\r\n * Used by: GSI_ActiveSessions\r\n * NOTE: Changed from global 'ACTIVE' to user-partitioned to prevent hot partition\r\n */\r\n ACTIVE_SESSION_PK: (userId: string) => `${PkPrefix.USER}${userId}`,\r\n\r\n /**\r\n * Active Sessions GSI SK\r\n * Pattern: {createdAt}#{sessionId}\r\n * Used by: GSI_ActiveSessions\r\n */\r\n ACTIVE_SESSION_SK: (createdAt: number, sessionId: string) => `${createdAt}${DELIMITER}${sessionId}`,\r\n\r\n // ============================================\r\n // GSI_UserSessions\r\n // ============================================\r\n\r\n /**\r\n * User Sessions GSI PK\r\n * Pattern: USR#{userId}\r\n * Used by: GSI_UserSessions\r\n */\r\n USER_SESSION_PK: (userId: string) => `${PkPrefix.USER}${userId}`,\r\n\r\n /**\r\n * User Sessions GSI SK\r\n * Pattern: {createdAt}#{sessionId}\r\n * Used by: GSI_UserSessions\r\n */\r\n USER_SESSION_SK: (createdAt: number, sessionId: string) => `${createdAt}${DELIMITER}${sessionId}`,\r\n\r\n // ============================================\r\n // GSI_UsersByStatus\r\n // ============================================\r\n\r\n /**\r\n * Users By Status GSI PK\r\n * Pattern: USR#ACTIVE or USR#INACTIVE (SCHEMA_V4)\r\n * Used by: GSI_UsersByStatus\r\n * Maps: active → ACTIVE, suspended/deleted → INACTIVE\r\n */\r\n USER_STATUS_PK: (status: string) => {\r\n // Map status to ACTIVE/INACTIVE\r\n const statusGroup = (status === USER_STATUS.ACTIVE) ? 'ACTIVE' : 'INACTIVE';\r\n return `${EntityPrefix.USER}${DELIMITER}${statusGroup}`;\r\n },\r\n\r\n /**\r\n * Users By Status GSI SK\r\n * Pattern: {userId}\r\n * Used by: GSI_UsersByStatus\r\n * Simple userId pattern to avoid needing createdAt for syncAuthStatus\r\n */\r\n USER_STATUS_SK: (userId: string) => userId,\r\n\r\n // ============================================\r\n // GSI_UserProviders\r\n // ============================================\r\n\r\n /**\r\n * User Providers GSI PK\r\n * Pattern: USR#{userId}\r\n * Used by: GSI_UserProviders\r\n */\r\n USER_PROVIDER_PK: (userId: string) => `${PkPrefix.USER}${userId}`,\r\n\r\n /**\r\n * User Providers GSI SK\r\n * Pattern: PRV#{provider}#{externalId}\r\n * Used by: GSI_UserProviders\r\n */\r\n USER_PROVIDER_SK: (provider: string, externalId: string) =>\r\n `${SkPrefix.PROVIDER}${provider}${DELIMITER}${externalId}`,\r\n\r\n} as const;\r\n\r\n/**\r\n * Table attribute names\r\n */\r\nexport const TableAttr = {\r\n PK: 'PK',\r\n SK: 'SK',\r\n TTL: 'TTL',\r\n ACTIVE_SESSION_PK: 'activeSessionPk', // GSI partition key for active sessions\r\n ACTIVE_SESSION_SK: 'activeSessionSk', // GSI sort key for active sessions (createdAt#sessionId)\r\n USER_SESSION_PK: 'userSessionPk', // GSI partition key for user sessions (USR#{userId})\r\n USER_SESSION_SK: 'userSessionSk', // GSI sort key for user sessions (createdAt#sessionId)\r\n USER_STATUS_PK: 'userStatusPk', // GSI partition key for users by status\r\n USER_STATUS_SK: 'userStatusSk', // GSI sort key for users by status (userId)\r\n USER_PROVIDER_PK: 'userProviderPk', // GSI partition key for user providers (USR#{userId})\r\n USER_PROVIDER_SK: 'userProviderSk', // GSI sort key for user providers (PRV#{provider}#{externalId})\r\n\r\n // Challenge attributes\r\n USER_ID: 'userId',\r\n TENANT_ID: 'tenantId',\r\n AUDIENCE: 'audience',\r\n PURPOSE: 'purpose',\r\n CHALLENGE_STATUS: 'challengeStatus', // Named challengeStatus to avoid DynamoDB reserved keyword 'status'\r\n PHASE: 'phase',\r\n ALLOWED_METHODS: 'allowedMethods',\r\n METHOD: 'method',\r\n IDENTITY_TYPE: 'identityType', // For credential challenges\r\n IDENTITY_VALUE: 'identityValue', // For credential challenges\r\n CODE_HASH: 'codeHash',\r\n ATTEMPTS: 'attempts',\r\n MAX_ATTEMPTS: 'maxAttempts',\r\n METADATA: 'metadata',\r\n FLOW_KEY: 'flowKey',\r\n EXPIRES_AT: 'expiresAt',\r\n CREATED_AT: 'createdAt',\r\n\r\n // Session attributes\r\n TOKEN_ID: 'tokenId',\r\n LAST_USED_AT: 'lastUsedAt',\r\n REVOKED_AT: 'revokedAt',\r\n PERM_MASK: 'permMask',\r\n ROLE_IDS: 'roleIds',\r\n DEVICE_LABEL: 'deviceLabel',\r\n COUNTRY: 'country',\r\n CITY: 'city',\r\n\r\n // Identity attributes\r\n IDENTITY_USER_PK: 'identityUserPk', // GSI partition key for user identities (USR#{userId})\r\n IDENTITY_USER_SK: 'identityUserSk', // GSI sort key for user identities (IDT#{type}#{value})\r\n LOGIN_ENABLED: 'loginEnabled',\r\n VERIFIED_AT: 'verifiedAt',\r\n GUARDS: 'guards',\r\n UPDATED_AT: 'updatedAt',\r\n\r\n // Credential attributes\r\n CREDENTIAL_USER_PK: 'credentialUserPk', // GSI partition key for user credentials (USR#{userId})\r\n CREDENTIAL_USER_SK: 'credentialUserSk', // GSI sort key for user credentials (CRD#{type}#{credentialId})\r\n CREDENTIAL_VALUE: 'value', // Credential value (password hash, etc.)\r\n // Note: REVOKED_AT is shared with Session (defined above)\r\n} as const;\r\n\r\n/**\r\n * Global Secondary Index Names\r\n * SCHEMA_V2: Reduced from 6 to 4 GSIs (EMAIL_LOOKUP and PHONE_LOOKUP replaced by LoginIdentity)\r\n *\r\n * NOTE: GSI_TenantMembers is NOT needed - TenantMember.get() uses GetItem (HOT path with known keys)\r\n */\r\nexport const GSIName = {\r\n ACTIVE_SESSIONS: 'GSI_ActiveSessions',\r\n USER_SESSIONS: 'GSI_UserSessions',\r\n USERS_BY_STATUS: 'GSI_UsersByStatus',\r\n USER_PROVIDERS: 'GSI_UserProviders',\r\n USER_IDENTITIES: 'GSI_UserIdentities', // Query all identities for a user\r\n USER_CREDENTIALS: 'GSI_UserCredentials', // Query all credentials for a user\r\n} as const;\r\n\r\n/**\r\n * GSI Key Helpers\r\n * Helper functions to build GSI keys\r\n */\r\nexport const GSIKeyHelper = {\r\n /**\r\n * Build session SK for GSI (ActiveSessions, UserSessions)\r\n * Pattern: {createdAtMillis}#{sessionId}\r\n * Example: 1705315800000#01HQZX8V9ABCDEFGHIJK\r\n * Allows sorting by creation time and ensures uniqueness\r\n */\r\n sessionSK: (createdAt: Date | number, sessionId: string): string => {\r\n const timestamp = createdAt instanceof Date ? createdAt.getTime() : createdAt;\r\n return `${timestamp}${DELIMITER}${sessionId}`;\r\n },\r\n\r\n /**\r\n * Extract createdAt (milliseconds) from session GSI SK\r\n * Pattern: {createdAtMillis}#{sessionId} -> createdAtMillis\r\n */\r\n extractCreatedAt: (gsiSK: string): number => {\r\n const delimiterIndex = gsiSK.indexOf(DELIMITER);\r\n const createdAtStr = delimiterIndex === -1 ? gsiSK : gsiSK.slice(0, delimiterIndex);\r\n return Number(createdAtStr);\r\n },\r\n} as const;\r\n","/**\r\n * Date Conversion Utilities\r\n * Convert between Date/Timestamp and Unix milliseconds/seconds\r\n */\r\n\r\n/**\r\n * Convert Date to Unix milliseconds (for storage)\r\n * @param date - JavaScript Date object\r\n * @returns Unix timestamp in milliseconds\r\n */\r\nexport function dateToMillis(date: Date): number {\r\n return date.getTime();\r\n}\r\n\r\n/**\r\n * Convert Unix milliseconds to Date\r\n * @param millis - Unix timestamp in milliseconds\r\n * @returns JavaScript Date object\r\n */\r\nexport function millisToDate(millis: number): Date {\r\n return new Date(millis);\r\n}\r\n\r\n/**\r\n * Convert Unix milliseconds to Unix seconds (for TTL)\r\n * @param millis - Unix timestamp in milliseconds\r\n * @returns Unix timestamp in seconds\r\n */\r\nfunction millisToSeconds(millis: number): number {\r\n return Math.floor(millis / 1000);\r\n}\r\n\r\n/**\r\n * Get current time in Unix milliseconds\r\n * @returns Current Unix timestamp in milliseconds\r\n */\r\nexport function now(): number {\r\n return Date.now();\r\n}\r\n\r\n/**\r\n * Calculate TTL timestamp (Unix seconds) from expiration date\r\n * @param expiresAt - Expiration date or timestamp in milliseconds\r\n * @returns Unix timestamp in seconds for DynamoDB TTL\r\n */\r\nexport function calculateTTL(expiresAt: Date | number): number {\r\n if (expiresAt instanceof Date) {\r\n return millisToSeconds(expiresAt.getTime());\r\n }\r\n return millisToSeconds(expiresAt);\r\n}\r\n\r\n/**\r\n * Check if a timestamp has expired\r\n * @param expiresAt - Expiration timestamp in milliseconds\r\n * @returns true if expired, false otherwise\r\n */\r\nexport function isExpired(expiresAt: number): boolean {\r\n return expiresAt < Date.now();\r\n}\r\n\r\n/**\r\n * Convert nullable Date to nullable number (milliseconds)\r\n * @param date - Date object or null\r\n * @returns Unix timestamp in milliseconds or null\r\n */\r\nexport function dateToMillisNullable(date: Date | null | undefined): number | null {\r\n if (!date) return null;\r\n return dateToMillis(date);\r\n}\r\n\r\n/**\r\n * Convert nullable number (milliseconds) to nullable Date\r\n * @param millis - Unix timestamp in milliseconds or null\r\n * @returns Date object or null\r\n */\r\nexport function millisToDateNullable(millis: number | null | undefined): Date | null {\r\n if (millis == null) return null;\r\n return millisToDate(millis);\r\n}\r\n","/**\r\n * DynamoDB TenantMember Repository Implementation\r\n */\r\n\r\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\r\nimport {\r\n DynamoDBDocumentClient,\r\n GetCommand,\r\n PutCommand,\r\n UpdateCommand,\r\n DeleteCommand,\r\n} from '@aws-sdk/lib-dynamodb';\r\nimport { ok, err, type Result } from 'ts-micro-result';\r\nimport type {\r\n TenantMemberRepository,\r\n TenantMember,\r\n TenantMemberStatus,\r\n CreateTenantMemberData,\r\n UpdateTenantMemberPermissionsData,\r\n TenantId,\r\n UserId,\r\n} from '@auth-craft/tenant-access-control';\r\nimport { tenantAccessErrors } from '@auth-craft/tenant-access-control';\r\nimport {\r\n KeyPattern,\r\n KeyExtractor,\r\n TableAttr,\r\n millisToDate,\r\n} from '@auth-craft/database-plugin-dynamodb';\r\n\r\nexport class DynamoDBTenantMemberRepository implements TenantMemberRepository {\r\n private docClient: DynamoDBDocumentClient;\r\n\r\n constructor(\r\n private client: DynamoDBClient,\r\n private tableName: string\r\n ) {\r\n this.docClient = DynamoDBDocumentClient.from(client, {\r\n marshallOptions: { removeUndefinedValues: true },\r\n });\r\n }\r\n\r\n async get(\r\n tenantId: TenantId,\r\n userId: UserId\r\n ): Promise<Result<TenantMember | null>> {\r\n try {\r\n const response = await this.docClient.send(\r\n new GetCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId),\r\n },\r\n })\r\n );\r\n\r\n if (!response.Item) {\r\n return ok(null);\r\n }\r\n\r\n return ok(this.mapItemToEntity(response.Item));\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async create(data: CreateTenantMemberData): Promise<Result<void>> {\r\n try {\r\n const now = Date.now();\r\n\r\n await this.docClient.send(\r\n new PutCommand({\r\n TableName: this.tableName,\r\n Item: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(data.tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(data.userId),\r\n userStatus: data.status,\r\n roleIds: JSON.stringify(data.roleIds),\r\n permMask: data.permMask ?? 0,\r\n createdAt: now,\r\n updatedAt: now,\r\n },\r\n ConditionExpression: 'attribute_not_exists(PK)',\r\n })\r\n );\r\n\r\n return ok();\r\n } catch (error: unknown) {\r\n if ((error as { name?: string }).name === 'ConditionalCheckFailedException') {\r\n return err(tenantAccessErrors.MEMBER_ALREADY_EXISTS());\r\n }\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async updatePermissions(\r\n tenantId: TenantId,\r\n userId: UserId,\r\n updates: UpdateTenantMemberPermissionsData\r\n ): Promise<Result<void>> {\r\n try {\r\n const setClauses: string[] = ['#updatedAt = :updatedAt'];\r\n const names: Record<string, string> = { '#updatedAt': 'updatedAt' };\r\n const values: Record<string, unknown> = { ':updatedAt': Date.now() };\r\n\r\n if (updates.roleIds !== undefined) {\r\n setClauses.push('#roleIds = :roleIds');\r\n names['#roleIds'] = 'roleIds';\r\n values[':roleIds'] = JSON.stringify(updates.roleIds);\r\n }\r\n\r\n if (updates.permMask !== undefined) {\r\n setClauses.push('#permMask = :permMask');\r\n names['#permMask'] = 'permMask';\r\n values[':permMask'] = updates.permMask;\r\n }\r\n\r\n await this.docClient.send(\r\n new UpdateCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId),\r\n },\r\n UpdateExpression: `SET ${setClauses.join(', ')}`,\r\n ExpressionAttributeNames: names,\r\n ExpressionAttributeValues: values,\r\n ConditionExpression: 'attribute_exists(PK)',\r\n })\r\n );\r\n\r\n return ok();\r\n } catch (error: unknown) {\r\n if ((error as { name?: string }).name === 'ConditionalCheckFailedException') {\r\n return err(tenantAccessErrors.MEMBER_NOT_FOUND());\r\n }\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async updateStatus(\r\n tenantId: TenantId,\r\n userId: UserId,\r\n status: TenantMemberStatus\r\n ): Promise<Result<void>> {\r\n try {\r\n await this.docClient.send(\r\n new UpdateCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId),\r\n },\r\n UpdateExpression: 'SET #status = :status, #updatedAt = :updatedAt',\r\n ExpressionAttributeNames: {\r\n '#status': 'userStatus',\r\n '#updatedAt': 'updatedAt',\r\n },\r\n ExpressionAttributeValues: {\r\n ':status': status,\r\n ':updatedAt': Date.now(),\r\n },\r\n ConditionExpression: 'attribute_exists(PK)',\r\n })\r\n );\r\n\r\n return ok();\r\n } catch (error: unknown) {\r\n if ((error as { name?: string }).name === 'ConditionalCheckFailedException') {\r\n return err(tenantAccessErrors.MEMBER_NOT_FOUND());\r\n }\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n async remove(tenantId: TenantId, userId: UserId): Promise<Result<void>> {\r\n try {\r\n await this.docClient.send(\r\n new DeleteCommand({\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),\r\n [TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(userId),\r\n },\r\n })\r\n );\r\n\r\n return ok();\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n\r\n private mapItemToEntity(item: Record<string, unknown>): TenantMember {\r\n const tenantId = KeyExtractor.tenantId(item[TableAttr.PK] as string) as TenantId;\r\n const userId = KeyExtractor.tenantMemberUserId(item[TableAttr.SK] as string) as UserId;\r\n\r\n return {\r\n tenantId,\r\n userId,\r\n status: item['userStatus'] as TenantMemberStatus,\r\n roleIds: JSON.parse(item['roleIds'] as string),\r\n permMask: (item['permMask'] as number) ?? 0,\r\n createdAt: millisToDate(item['createdAt'] as number),\r\n updatedAt: millisToDate(item['updatedAt'] as number),\r\n };\r\n }\r\n}\r\n","/**\r\n * DynamoDB Session Repository Implementation (Limited)\r\n *\r\n * Only implements methods needed for tenant access control:\r\n * - findActiveByUserId: Query active sessions\r\n * - revokeBatch: Batch revoke sessions\r\n */\r\n\r\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\r\nimport {\r\n DynamoDBDocumentClient,\r\n QueryCommand,\r\n TransactWriteCommand,\r\n} from '@aws-sdk/lib-dynamodb';\r\nimport { ok, err, type Result, type PaginatedResult, type CursorPagination } from 'ts-micro-result';\r\nimport type {\r\n SessionRepository,\r\n SessionInfo,\r\n SessionId,\r\n UserId,\r\n TenantId,\r\n} from '@auth-craft/tenant-access-control';\r\nimport { tenantAccessErrors } from '@auth-craft/tenant-access-control';\r\nimport {\r\n KeyPattern,\r\n KeyExtractor,\r\n TableAttr,\r\n GSIName,\r\n GSIKeys,\r\n} from '@auth-craft/database-plugin-dynamodb';\r\n\r\nexport class DynamoDBSessionRepository implements SessionRepository {\r\n private docClient: DynamoDBDocumentClient;\r\n\r\n constructor(\r\n private client: DynamoDBClient,\r\n private tableName: string\r\n ) {\r\n this.docClient = DynamoDBDocumentClient.from(client);\r\n }\r\n\r\n async findActiveByUserId(\r\n userId: UserId,\r\n options?: { limit?: number; cursor?: string }\r\n ): Promise<PaginatedResult<SessionInfo>> {\r\n try {\r\n const limit = Math.min(options?.limit ?? 50, 100);\r\n let exclusiveStartKey: Record<string, unknown> | undefined;\r\n\r\n if (options?.cursor) {\r\n try {\r\n exclusiveStartKey = JSON.parse(\r\n Buffer.from(options.cursor, 'base64url').toString('utf-8')\r\n );\r\n } catch {\r\n return err(tenantAccessErrors.INVALID_CURSOR()) as PaginatedResult<SessionInfo>;\r\n }\r\n }\r\n\r\n const response = await this.docClient.send(\r\n new QueryCommand({\r\n TableName: this.tableName,\r\n IndexName: GSIName.ACTIVE_SESSIONS,\r\n KeyConditionExpression: `${TableAttr.ACTIVE_SESSION_PK} = :activePk`,\r\n ExpressionAttributeValues: {\r\n ':activePk': GSIKeys.ACTIVE_SESSION_PK(userId),\r\n },\r\n ProjectionExpression: `${TableAttr.PK}, ${TableAttr.TENANT_ID}, ${TableAttr.AUDIENCE}, ${TableAttr.USER_ID}`,\r\n Limit: limit,\r\n ExclusiveStartKey: exclusiveStartKey,\r\n ScanIndexForward: false,\r\n })\r\n );\r\n\r\n const sessions: SessionInfo[] = (response.Items ?? []).map((item) => ({\r\n id: KeyExtractor.sessionId(item[TableAttr.PK] as string) as SessionId,\r\n userId: item[TableAttr.USER_ID] as UserId,\r\n tenantId: item[TableAttr.TENANT_ID] as TenantId | undefined,\r\n audience: item[TableAttr.AUDIENCE] as string,\r\n }));\r\n\r\n const hasNext = !!response.LastEvaluatedKey;\r\n const cursor = hasNext\r\n ? Buffer.from(JSON.stringify(response.LastEvaluatedKey)).toString('base64url')\r\n : null;\r\n\r\n const pagination: CursorPagination = {\r\n type: 'cursor',\r\n limit,\r\n count: sessions.length,\r\n hasNext,\r\n hasPrev: !!options?.cursor,\r\n cursor,\r\n };\r\n\r\n return ok(sessions, { pagination }) as PaginatedResult<SessionInfo>;\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR()) as PaginatedResult<SessionInfo>;\r\n }\r\n }\r\n\r\n async revokeBatch(sessionIds: SessionId[]): Promise<Result<number>> {\r\n if (sessionIds.length === 0) {\r\n return ok(0);\r\n }\r\n\r\n try {\r\n const revokedAt = Date.now();\r\n let count = 0;\r\n const batchSize = 25;\r\n\r\n for (let i = 0; i < sessionIds.length; i += batchSize) {\r\n const batch = sessionIds.slice(i, i + batchSize);\r\n\r\n const transactItems = batch.map((sessionId) => ({\r\n Update: {\r\n TableName: this.tableName,\r\n Key: {\r\n [TableAttr.PK]: KeyPattern.SESSION_PK(sessionId),\r\n [TableAttr.SK]: KeyPattern.SESSION_SK(),\r\n },\r\n UpdateExpression: `SET #revokedAt = :revokedAt REMOVE #activePk, #activeSk`,\r\n ExpressionAttributeNames: {\r\n '#revokedAt': TableAttr.REVOKED_AT,\r\n '#activePk': TableAttr.ACTIVE_SESSION_PK,\r\n '#activeSk': TableAttr.ACTIVE_SESSION_SK,\r\n },\r\n ExpressionAttributeValues: {\r\n ':revokedAt': revokedAt,\r\n },\r\n ConditionExpression: `attribute_exists(${TableAttr.PK}) AND attribute_not_exists(#revokedAt)`,\r\n },\r\n }));\r\n\r\n try {\r\n await this.docClient.send(\r\n new TransactWriteCommand({ TransactItems: transactItems })\r\n );\r\n count += batch.length;\r\n } catch {\r\n // Some sessions may already be revoked, continue with remaining batches\r\n }\r\n }\r\n\r\n return ok(count);\r\n } catch {\r\n return err(tenantAccessErrors.DATABASE_ERROR());\r\n }\r\n }\r\n}\r\n","/**\r\n * @auth-craft/tenant-access-control-dynamodb\r\n *\r\n * DynamoDB implementation for tenant-access-control.\r\n *\r\n * @example\r\n * ```typescript\r\n * import { createTenantAccessSDK } from '@auth-craft/tenant-access-control';\r\n * import { createTenantAccessDynamoDBPlugin } from '@auth-craft/tenant-access-control-dynamodb';\r\n *\r\n * // Create DynamoDB plugin\r\n * const plugin = createTenantAccessDynamoDBPlugin({\r\n * tableName: 'auth-table',\r\n * });\r\n *\r\n * // Create SDK with DynamoDB implementation\r\n * const sdk = createTenantAccessSDK(plugin);\r\n * ```\r\n */\r\n\r\nimport { DynamoDBClient } from '@aws-sdk/client-dynamodb';\r\nimport type { TenantAccessControlDeps } from '@auth-craft/tenant-access-control';\r\nimport { DynamoDBTenantMemberRepository } from './dynamodb-tenant-member-repo';\r\nimport { DynamoDBSessionRepository } from './dynamodb-session-repo';\r\nimport type { TenantAccessDynamoDBConfig } from './config';\r\n\r\n/**\r\n * Create DynamoDB plugin for Tenant Access Control\r\n *\r\n * @param config - DynamoDB configuration\r\n * @returns TenantAccessControlDeps - Dependencies for createTenantAccessSDK\r\n */\r\nexport function createTenantAccessDynamoDBPlugin(\r\n config: TenantAccessDynamoDBConfig\r\n): TenantAccessControlDeps {\r\n const client = config.client ?? new DynamoDBClient({});\r\n\r\n return {\r\n tenantMemberRepository: new DynamoDBTenantMemberRepository(client, config.tableName),\r\n sessionRepository: new DynamoDBSessionRepository(client, config.tableName),\r\n };\r\n}\r\n\r\n// Export config type\r\nexport type { TenantAccessDynamoDBConfig } from './config';\r\n\r\n// Export repositories for advanced use cases\r\nexport { DynamoDBTenantMemberRepository } from './dynamodb-tenant-member-repo';\r\nexport { DynamoDBSessionRepository } from './dynamodb-session-repo';\r\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -27,11 +27,11 @@ declare class DynamoDBTenantMemberRepository implements TenantMemberRepository {
|
|
|
27
27
|
private tableName;
|
|
28
28
|
private docClient;
|
|
29
29
|
constructor(client: DynamoDBClient, tableName: string);
|
|
30
|
-
get(tenantId: TenantId,
|
|
30
|
+
get(tenantId: TenantId, userId: UserId): Promise<Result<TenantMember | null>>;
|
|
31
31
|
create(data: CreateTenantMemberData): Promise<Result<void>>;
|
|
32
|
-
updatePermissions(tenantId: TenantId,
|
|
33
|
-
updateStatus(tenantId: TenantId,
|
|
34
|
-
remove(tenantId: TenantId,
|
|
32
|
+
updatePermissions(tenantId: TenantId, userId: UserId, updates: UpdateTenantMemberPermissionsData): Promise<Result<void>>;
|
|
33
|
+
updateStatus(tenantId: TenantId, userId: UserId, status: TenantMemberStatus): Promise<Result<void>>;
|
|
34
|
+
remove(tenantId: TenantId, userId: UserId): Promise<Result<void>>;
|
|
35
35
|
private mapItemToEntity;
|
|
36
36
|
}
|
|
37
37
|
|
package/dist/index.d.ts
CHANGED
|
@@ -27,11 +27,11 @@ declare class DynamoDBTenantMemberRepository implements TenantMemberRepository {
|
|
|
27
27
|
private tableName;
|
|
28
28
|
private docClient;
|
|
29
29
|
constructor(client: DynamoDBClient, tableName: string);
|
|
30
|
-
get(tenantId: TenantId,
|
|
30
|
+
get(tenantId: TenantId, userId: UserId): Promise<Result<TenantMember | null>>;
|
|
31
31
|
create(data: CreateTenantMemberData): Promise<Result<void>>;
|
|
32
|
-
updatePermissions(tenantId: TenantId,
|
|
33
|
-
updateStatus(tenantId: TenantId,
|
|
34
|
-
remove(tenantId: TenantId,
|
|
32
|
+
updatePermissions(tenantId: TenantId, userId: UserId, updates: UpdateTenantMemberPermissionsData): Promise<Result<void>>;
|
|
33
|
+
updateStatus(tenantId: TenantId, userId: UserId, status: TenantMemberStatus): Promise<Result<void>>;
|
|
34
|
+
remove(tenantId: TenantId, userId: UserId): Promise<Result<void>>;
|
|
35
35
|
private mapItemToEntity;
|
|
36
36
|
}
|
|
37
37
|
|