@auth-craft/tenant-access-control-dynamodb 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -0
- package/dist/index.cjs +272 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +86 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +268 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# @auth-craft/tenant-access-control-dynamodb
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Experimental / Internal Use**
|
|
4
|
+
>
|
|
5
|
+
> This package is published for convenience only.
|
|
6
|
+
>
|
|
7
|
+
> - No stability guarantee
|
|
8
|
+
> - Breaking changes may happen at any time
|
|
9
|
+
> - No documentation
|
|
10
|
+
> - No support
|
|
11
|
+
>
|
|
12
|
+
> Use at your own risk.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
DynamoDB implementation for `@auth-craft/tenant-access-control`.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @auth-craft/tenant-access-control @auth-craft/tenant-access-control-dynamodb
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { createTenantAccessSDK } from '@auth-craft/tenant-access-control';
|
|
28
|
+
import { createTenantAccessDynamoDBPlugin } from '@auth-craft/tenant-access-control-dynamodb';
|
|
29
|
+
|
|
30
|
+
// Create DynamoDB plugin
|
|
31
|
+
const plugin = createTenantAccessDynamoDBPlugin({
|
|
32
|
+
tableName: 'your-auth-table',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Create SDK with DynamoDB implementation
|
|
36
|
+
const sdk = createTenantAccessSDK(plugin);
|
|
37
|
+
|
|
38
|
+
// Use SDK
|
|
39
|
+
const member = await sdk.getMember({ ... });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
### `createTenantAccessDynamoDBPlugin(config)`
|
|
45
|
+
|
|
46
|
+
**Parameters:**
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
interface TenantAccessDynamoDBConfig {
|
|
50
|
+
// DynamoDB table name (required)
|
|
51
|
+
tableName: string;
|
|
52
|
+
|
|
53
|
+
// Optional: Provide your own DynamoDB client
|
|
54
|
+
// If not provided, a new client will be created
|
|
55
|
+
client?: DynamoDBClient;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### With Custom Client
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
63
|
+
|
|
64
|
+
const client = new DynamoDBClient({
|
|
65
|
+
region: 'ap-southeast-1',
|
|
66
|
+
credentials: {
|
|
67
|
+
accessKeyId: '...',
|
|
68
|
+
secretAccessKey: '...',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const plugin = createTenantAccessDynamoDBPlugin({
|
|
73
|
+
tableName: 'auth-table',
|
|
74
|
+
client,
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### With Local DynamoDB
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const client = new DynamoDBClient({
|
|
82
|
+
endpoint: 'http://localhost:8000',
|
|
83
|
+
region: 'local',
|
|
84
|
+
credentials: {
|
|
85
|
+
accessKeyId: 'local',
|
|
86
|
+
secretAccessKey: 'local',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const plugin = createTenantAccessDynamoDBPlugin({
|
|
91
|
+
tableName: 'auth-table',
|
|
92
|
+
client,
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Table Schema
|
|
97
|
+
|
|
98
|
+
This package uses the same DynamoDB table schema as `@auth-craft/database-plugin-dynamodb`.
|
|
99
|
+
|
|
100
|
+
### TenantMember Item
|
|
101
|
+
|
|
102
|
+
| Attribute | Value |
|
|
103
|
+
|-----------|-------|
|
|
104
|
+
| PK | `TNT#{tenantId}` |
|
|
105
|
+
| SK | `AUD#{audience}#USR#{userId}` |
|
|
106
|
+
| userStatus | `invited` \| `active` \| `suspended` \| `removed` |
|
|
107
|
+
| roleIds | JSON stringified array |
|
|
108
|
+
| permMask | number |
|
|
109
|
+
| createdAt | Unix milliseconds |
|
|
110
|
+
| updatedAt | Unix milliseconds |
|
|
111
|
+
|
|
112
|
+
### Session Item
|
|
113
|
+
|
|
114
|
+
Uses existing session items with:
|
|
115
|
+
- `tenantId` - Tenant identifier
|
|
116
|
+
- `audience` - Target audience
|
|
117
|
+
- GSI_ActiveSessions for querying active sessions
|
|
118
|
+
|
|
119
|
+
## Advanced Usage
|
|
120
|
+
|
|
121
|
+
### Direct Repository Access
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import {
|
|
125
|
+
DynamoDBTenantMemberRepository,
|
|
126
|
+
DynamoDBSessionRepository,
|
|
127
|
+
} from '@auth-craft/tenant-access-control-dynamodb';
|
|
128
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
129
|
+
|
|
130
|
+
const client = new DynamoDBClient({});
|
|
131
|
+
|
|
132
|
+
const tenantMemberRepo = new DynamoDBTenantMemberRepository(client, 'auth-table');
|
|
133
|
+
const sessionRepo = new DynamoDBSessionRepository(client, 'auth-table');
|
|
134
|
+
|
|
135
|
+
// Use repositories directly
|
|
136
|
+
const member = await tenantMemberRepo.get(tenantId, audience, userId);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Dependencies
|
|
140
|
+
|
|
141
|
+
- `@auth-craft/tenant-access-control` - SDK and interfaces
|
|
142
|
+
- `@auth-craft/database-plugin-dynamodb` - Key patterns and utilities
|
|
143
|
+
- `@aws-sdk/client-dynamodb` - AWS SDK DynamoDB client
|
|
144
|
+
- `@aws-sdk/lib-dynamodb` - AWS SDK DynamoDB Document client
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var clientDynamodb = require('@aws-sdk/client-dynamodb');
|
|
4
|
+
var libDynamodb = require('@aws-sdk/lib-dynamodb');
|
|
5
|
+
var tsMicroResult = require('ts-micro-result');
|
|
6
|
+
var tenantAccessControl = require('@auth-craft/tenant-access-control');
|
|
7
|
+
var databasePluginDynamodb = require('@auth-craft/database-plugin-dynamodb');
|
|
8
|
+
|
|
9
|
+
// src/index.ts
|
|
10
|
+
var DynamoDBTenantMemberRepository = class {
|
|
11
|
+
constructor(client, tableName) {
|
|
12
|
+
this.client = client;
|
|
13
|
+
this.tableName = tableName;
|
|
14
|
+
this.docClient = libDynamodb.DynamoDBDocumentClient.from(client, {
|
|
15
|
+
marshallOptions: { removeUndefinedValues: true }
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
docClient;
|
|
19
|
+
async get(tenantId, audience, userId) {
|
|
20
|
+
try {
|
|
21
|
+
const response = await this.docClient.send(
|
|
22
|
+
new libDynamodb.GetCommand({
|
|
23
|
+
TableName: this.tableName,
|
|
24
|
+
Key: {
|
|
25
|
+
[databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
26
|
+
[databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
);
|
|
30
|
+
if (!response.Item) {
|
|
31
|
+
return tsMicroResult.ok(null);
|
|
32
|
+
}
|
|
33
|
+
return tsMicroResult.ok(this.mapItemToEntity(response.Item));
|
|
34
|
+
} catch {
|
|
35
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async create(data) {
|
|
39
|
+
try {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
await this.docClient.send(
|
|
42
|
+
new libDynamodb.PutCommand({
|
|
43
|
+
TableName: this.tableName,
|
|
44
|
+
Item: {
|
|
45
|
+
[databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(data.tenantId),
|
|
46
|
+
[databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(data.audience, data.userId),
|
|
47
|
+
userStatus: data.status,
|
|
48
|
+
roleIds: JSON.stringify(data.roleIds),
|
|
49
|
+
permMask: data.permMask ?? 0,
|
|
50
|
+
createdAt: now,
|
|
51
|
+
updatedAt: now
|
|
52
|
+
},
|
|
53
|
+
ConditionExpression: "attribute_not_exists(PK)"
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
return tsMicroResult.ok();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.name === "ConditionalCheckFailedException") {
|
|
59
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.MEMBER_ALREADY_EXISTS());
|
|
60
|
+
}
|
|
61
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async updatePermissions(tenantId, audience, userId, updates) {
|
|
65
|
+
try {
|
|
66
|
+
const setClauses = ["#updatedAt = :updatedAt"];
|
|
67
|
+
const names = { "#updatedAt": "updatedAt" };
|
|
68
|
+
const values = { ":updatedAt": Date.now() };
|
|
69
|
+
if (updates.roleIds !== void 0) {
|
|
70
|
+
setClauses.push("#roleIds = :roleIds");
|
|
71
|
+
names["#roleIds"] = "roleIds";
|
|
72
|
+
values[":roleIds"] = JSON.stringify(updates.roleIds);
|
|
73
|
+
}
|
|
74
|
+
if (updates.permMask !== void 0) {
|
|
75
|
+
setClauses.push("#permMask = :permMask");
|
|
76
|
+
names["#permMask"] = "permMask";
|
|
77
|
+
values[":permMask"] = updates.permMask;
|
|
78
|
+
}
|
|
79
|
+
await this.docClient.send(
|
|
80
|
+
new libDynamodb.UpdateCommand({
|
|
81
|
+
TableName: this.tableName,
|
|
82
|
+
Key: {
|
|
83
|
+
[databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
84
|
+
[databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
85
|
+
},
|
|
86
|
+
UpdateExpression: `SET ${setClauses.join(", ")}`,
|
|
87
|
+
ExpressionAttributeNames: names,
|
|
88
|
+
ExpressionAttributeValues: values,
|
|
89
|
+
ConditionExpression: "attribute_exists(PK)"
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
return tsMicroResult.ok();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.name === "ConditionalCheckFailedException") {
|
|
95
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.MEMBER_NOT_FOUND());
|
|
96
|
+
}
|
|
97
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async updateStatus(tenantId, audience, userId, status) {
|
|
101
|
+
try {
|
|
102
|
+
await this.docClient.send(
|
|
103
|
+
new libDynamodb.UpdateCommand({
|
|
104
|
+
TableName: this.tableName,
|
|
105
|
+
Key: {
|
|
106
|
+
[databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
107
|
+
[databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
108
|
+
},
|
|
109
|
+
UpdateExpression: "SET #status = :status, #updatedAt = :updatedAt",
|
|
110
|
+
ExpressionAttributeNames: {
|
|
111
|
+
"#status": "userStatus",
|
|
112
|
+
"#updatedAt": "updatedAt"
|
|
113
|
+
},
|
|
114
|
+
ExpressionAttributeValues: {
|
|
115
|
+
":status": status,
|
|
116
|
+
":updatedAt": Date.now()
|
|
117
|
+
},
|
|
118
|
+
ConditionExpression: "attribute_exists(PK)"
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
return tsMicroResult.ok();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error.name === "ConditionalCheckFailedException") {
|
|
124
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.MEMBER_NOT_FOUND());
|
|
125
|
+
}
|
|
126
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async remove(tenantId, audience, userId) {
|
|
130
|
+
try {
|
|
131
|
+
await this.docClient.send(
|
|
132
|
+
new libDynamodb.DeleteCommand({
|
|
133
|
+
TableName: this.tableName,
|
|
134
|
+
Key: {
|
|
135
|
+
[databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
136
|
+
[databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
return tsMicroResult.ok();
|
|
141
|
+
} catch {
|
|
142
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
mapItemToEntity(item) {
|
|
146
|
+
const tenantId = databasePluginDynamodb.KeyExtractor.tenantId(item[databasePluginDynamodb.TableAttr.PK]);
|
|
147
|
+
const skInfo = databasePluginDynamodb.KeyExtractor.tenantMemberSKInfo(item[databasePluginDynamodb.TableAttr.SK]);
|
|
148
|
+
return {
|
|
149
|
+
tenantId,
|
|
150
|
+
audience: skInfo?.audience ?? "",
|
|
151
|
+
userId: skInfo?.userId ?? "",
|
|
152
|
+
status: item["userStatus"],
|
|
153
|
+
roleIds: JSON.parse(item["roleIds"]),
|
|
154
|
+
permMask: item["permMask"] ?? 0,
|
|
155
|
+
createdAt: databasePluginDynamodb.millisToDate(item["createdAt"]),
|
|
156
|
+
updatedAt: databasePluginDynamodb.millisToDate(item["updatedAt"])
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var DynamoDBSessionRepository = class {
|
|
161
|
+
constructor(client, tableName) {
|
|
162
|
+
this.client = client;
|
|
163
|
+
this.tableName = tableName;
|
|
164
|
+
this.docClient = libDynamodb.DynamoDBDocumentClient.from(client);
|
|
165
|
+
}
|
|
166
|
+
docClient;
|
|
167
|
+
async findActiveByUserId(userId, options) {
|
|
168
|
+
try {
|
|
169
|
+
const limit = Math.min(options?.limit ?? 50, 100);
|
|
170
|
+
let exclusiveStartKey;
|
|
171
|
+
if (options?.cursor) {
|
|
172
|
+
try {
|
|
173
|
+
exclusiveStartKey = JSON.parse(
|
|
174
|
+
Buffer.from(options.cursor, "base64url").toString("utf-8")
|
|
175
|
+
);
|
|
176
|
+
} catch {
|
|
177
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.INVALID_CURSOR());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const response = await this.docClient.send(
|
|
181
|
+
new libDynamodb.QueryCommand({
|
|
182
|
+
TableName: this.tableName,
|
|
183
|
+
IndexName: databasePluginDynamodb.GSIName.ACTIVE_SESSIONS,
|
|
184
|
+
KeyConditionExpression: `${databasePluginDynamodb.TableAttr.ACTIVE_SESSION_PK} = :activePk`,
|
|
185
|
+
ExpressionAttributeValues: {
|
|
186
|
+
":activePk": databasePluginDynamodb.GSIKeys.ACTIVE_SESSION_PK(userId)
|
|
187
|
+
},
|
|
188
|
+
ProjectionExpression: `${databasePluginDynamodb.TableAttr.PK}, ${databasePluginDynamodb.TableAttr.TENANT_ID}, ${databasePluginDynamodb.TableAttr.AUDIENCE}, ${databasePluginDynamodb.TableAttr.USER_ID}`,
|
|
189
|
+
Limit: limit,
|
|
190
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
191
|
+
ScanIndexForward: false
|
|
192
|
+
})
|
|
193
|
+
);
|
|
194
|
+
const sessions = (response.Items ?? []).map((item) => ({
|
|
195
|
+
id: databasePluginDynamodb.KeyExtractor.sessionId(item[databasePluginDynamodb.TableAttr.PK]),
|
|
196
|
+
userId: item[databasePluginDynamodb.TableAttr.USER_ID],
|
|
197
|
+
tenantId: item[databasePluginDynamodb.TableAttr.TENANT_ID],
|
|
198
|
+
audience: item[databasePluginDynamodb.TableAttr.AUDIENCE]
|
|
199
|
+
}));
|
|
200
|
+
const hasNext = !!response.LastEvaluatedKey;
|
|
201
|
+
const cursor = hasNext ? Buffer.from(JSON.stringify(response.LastEvaluatedKey)).toString("base64url") : null;
|
|
202
|
+
const pagination = {
|
|
203
|
+
type: "cursor",
|
|
204
|
+
limit,
|
|
205
|
+
count: sessions.length,
|
|
206
|
+
hasNext,
|
|
207
|
+
hasPrev: !!options?.cursor,
|
|
208
|
+
cursor
|
|
209
|
+
};
|
|
210
|
+
return tsMicroResult.ok(sessions, { pagination });
|
|
211
|
+
} catch {
|
|
212
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async revokeBatch(sessionIds) {
|
|
216
|
+
if (sessionIds.length === 0) {
|
|
217
|
+
return tsMicroResult.ok(0);
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
const revokedAt = Date.now();
|
|
221
|
+
let count = 0;
|
|
222
|
+
const batchSize = 25;
|
|
223
|
+
for (let i = 0; i < sessionIds.length; i += batchSize) {
|
|
224
|
+
const batch = sessionIds.slice(i, i + batchSize);
|
|
225
|
+
const transactItems = batch.map((sessionId) => ({
|
|
226
|
+
Update: {
|
|
227
|
+
TableName: this.tableName,
|
|
228
|
+
Key: {
|
|
229
|
+
[databasePluginDynamodb.TableAttr.PK]: databasePluginDynamodb.KeyPattern.SESSION_PK(sessionId),
|
|
230
|
+
[databasePluginDynamodb.TableAttr.SK]: databasePluginDynamodb.KeyPattern.SESSION_SK()
|
|
231
|
+
},
|
|
232
|
+
UpdateExpression: `SET #revokedAt = :revokedAt REMOVE #activePk, #activeSk`,
|
|
233
|
+
ExpressionAttributeNames: {
|
|
234
|
+
"#revokedAt": databasePluginDynamodb.TableAttr.REVOKED_AT,
|
|
235
|
+
"#activePk": databasePluginDynamodb.TableAttr.ACTIVE_SESSION_PK,
|
|
236
|
+
"#activeSk": databasePluginDynamodb.TableAttr.ACTIVE_SESSION_SK
|
|
237
|
+
},
|
|
238
|
+
ExpressionAttributeValues: {
|
|
239
|
+
":revokedAt": revokedAt
|
|
240
|
+
},
|
|
241
|
+
ConditionExpression: `attribute_exists(${databasePluginDynamodb.TableAttr.PK}) AND attribute_not_exists(#revokedAt)`
|
|
242
|
+
}
|
|
243
|
+
}));
|
|
244
|
+
try {
|
|
245
|
+
await this.docClient.send(
|
|
246
|
+
new libDynamodb.TransactWriteCommand({ TransactItems: transactItems })
|
|
247
|
+
);
|
|
248
|
+
count += batch.length;
|
|
249
|
+
} catch {
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return tsMicroResult.ok(count);
|
|
253
|
+
} catch {
|
|
254
|
+
return tsMicroResult.err(tenantAccessControl.tenantAccessErrors.DATABASE_ERROR());
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// src/index.ts
|
|
260
|
+
function createTenantAccessDynamoDBPlugin(config) {
|
|
261
|
+
const client = config.client ?? new clientDynamodb.DynamoDBClient({});
|
|
262
|
+
return {
|
|
263
|
+
tenantMemberRepository: new DynamoDBTenantMemberRepository(client, config.tableName),
|
|
264
|
+
sessionRepository: new DynamoDBSessionRepository(client, config.tableName)
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
exports.DynamoDBSessionRepository = DynamoDBSessionRepository;
|
|
269
|
+
exports.DynamoDBTenantMemberRepository = DynamoDBTenantMemberRepository;
|
|
270
|
+
exports.createTenantAccessDynamoDBPlugin = createTenantAccessDynamoDBPlugin;
|
|
271
|
+
//# sourceMappingURL=index.cjs.map
|
|
272
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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":"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","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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { TenantMemberRepository, TenantId, UserId, TenantMember, CreateTenantMemberData, UpdateTenantMemberPermissionsData, TenantMemberStatus, SessionRepository, SessionInfo, SessionId, TenantAccessControlDeps } from '@auth-craft/tenant-access-control';
|
|
2
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
+
import { Result, PaginatedResult } from 'ts-micro-result';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for Tenant Access Control DynamoDB Plugin
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface TenantAccessDynamoDBConfig {
|
|
10
|
+
/**
|
|
11
|
+
* DynamoDB table name
|
|
12
|
+
*/
|
|
13
|
+
tableName: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional DynamoDB client instance
|
|
16
|
+
* If not provided, a new client will be created
|
|
17
|
+
*/
|
|
18
|
+
client?: DynamoDBClient;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* DynamoDB TenantMember Repository Implementation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
declare class DynamoDBTenantMemberRepository implements TenantMemberRepository {
|
|
26
|
+
private client;
|
|
27
|
+
private tableName;
|
|
28
|
+
private docClient;
|
|
29
|
+
constructor(client: DynamoDBClient, tableName: string);
|
|
30
|
+
get(tenantId: TenantId, audience: string, userId: UserId): Promise<Result<TenantMember | null>>;
|
|
31
|
+
create(data: CreateTenantMemberData): Promise<Result<void>>;
|
|
32
|
+
updatePermissions(tenantId: TenantId, audience: string, userId: UserId, updates: UpdateTenantMemberPermissionsData): Promise<Result<void>>;
|
|
33
|
+
updateStatus(tenantId: TenantId, audience: string, userId: UserId, status: TenantMemberStatus): Promise<Result<void>>;
|
|
34
|
+
remove(tenantId: TenantId, audience: string, userId: UserId): Promise<Result<void>>;
|
|
35
|
+
private mapItemToEntity;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* DynamoDB Session Repository Implementation (Limited)
|
|
40
|
+
*
|
|
41
|
+
* Only implements methods needed for tenant access control:
|
|
42
|
+
* - findActiveByUserId: Query active sessions
|
|
43
|
+
* - revokeBatch: Batch revoke sessions
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
declare class DynamoDBSessionRepository implements SessionRepository {
|
|
47
|
+
private client;
|
|
48
|
+
private tableName;
|
|
49
|
+
private docClient;
|
|
50
|
+
constructor(client: DynamoDBClient, tableName: string);
|
|
51
|
+
findActiveByUserId(userId: UserId, options?: {
|
|
52
|
+
limit?: number;
|
|
53
|
+
cursor?: string;
|
|
54
|
+
}): Promise<PaginatedResult<SessionInfo>>;
|
|
55
|
+
revokeBatch(sessionIds: SessionId[]): Promise<Result<number>>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @auth-craft/tenant-access-control-dynamodb
|
|
60
|
+
*
|
|
61
|
+
* DynamoDB implementation for tenant-access-control.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import { createTenantAccessSDK } from '@auth-craft/tenant-access-control';
|
|
66
|
+
* import { createTenantAccessDynamoDBPlugin } from '@auth-craft/tenant-access-control-dynamodb';
|
|
67
|
+
*
|
|
68
|
+
* // Create DynamoDB plugin
|
|
69
|
+
* const plugin = createTenantAccessDynamoDBPlugin({
|
|
70
|
+
* tableName: 'auth-table',
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Create SDK with DynamoDB implementation
|
|
74
|
+
* const sdk = createTenantAccessSDK(plugin);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create DynamoDB plugin for Tenant Access Control
|
|
80
|
+
*
|
|
81
|
+
* @param config - DynamoDB configuration
|
|
82
|
+
* @returns TenantAccessControlDeps - Dependencies for createTenantAccessSDK
|
|
83
|
+
*/
|
|
84
|
+
declare function createTenantAccessDynamoDBPlugin(config: TenantAccessDynamoDBConfig): TenantAccessControlDeps;
|
|
85
|
+
|
|
86
|
+
export { DynamoDBSessionRepository, DynamoDBTenantMemberRepository, type TenantAccessDynamoDBConfig, createTenantAccessDynamoDBPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { TenantMemberRepository, TenantId, UserId, TenantMember, CreateTenantMemberData, UpdateTenantMemberPermissionsData, TenantMemberStatus, SessionRepository, SessionInfo, SessionId, TenantAccessControlDeps } from '@auth-craft/tenant-access-control';
|
|
2
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
+
import { Result, PaginatedResult } from 'ts-micro-result';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for Tenant Access Control DynamoDB Plugin
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface TenantAccessDynamoDBConfig {
|
|
10
|
+
/**
|
|
11
|
+
* DynamoDB table name
|
|
12
|
+
*/
|
|
13
|
+
tableName: string;
|
|
14
|
+
/**
|
|
15
|
+
* Optional DynamoDB client instance
|
|
16
|
+
* If not provided, a new client will be created
|
|
17
|
+
*/
|
|
18
|
+
client?: DynamoDBClient;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* DynamoDB TenantMember Repository Implementation
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
declare class DynamoDBTenantMemberRepository implements TenantMemberRepository {
|
|
26
|
+
private client;
|
|
27
|
+
private tableName;
|
|
28
|
+
private docClient;
|
|
29
|
+
constructor(client: DynamoDBClient, tableName: string);
|
|
30
|
+
get(tenantId: TenantId, audience: string, userId: UserId): Promise<Result<TenantMember | null>>;
|
|
31
|
+
create(data: CreateTenantMemberData): Promise<Result<void>>;
|
|
32
|
+
updatePermissions(tenantId: TenantId, audience: string, userId: UserId, updates: UpdateTenantMemberPermissionsData): Promise<Result<void>>;
|
|
33
|
+
updateStatus(tenantId: TenantId, audience: string, userId: UserId, status: TenantMemberStatus): Promise<Result<void>>;
|
|
34
|
+
remove(tenantId: TenantId, audience: string, userId: UserId): Promise<Result<void>>;
|
|
35
|
+
private mapItemToEntity;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* DynamoDB Session Repository Implementation (Limited)
|
|
40
|
+
*
|
|
41
|
+
* Only implements methods needed for tenant access control:
|
|
42
|
+
* - findActiveByUserId: Query active sessions
|
|
43
|
+
* - revokeBatch: Batch revoke sessions
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
declare class DynamoDBSessionRepository implements SessionRepository {
|
|
47
|
+
private client;
|
|
48
|
+
private tableName;
|
|
49
|
+
private docClient;
|
|
50
|
+
constructor(client: DynamoDBClient, tableName: string);
|
|
51
|
+
findActiveByUserId(userId: UserId, options?: {
|
|
52
|
+
limit?: number;
|
|
53
|
+
cursor?: string;
|
|
54
|
+
}): Promise<PaginatedResult<SessionInfo>>;
|
|
55
|
+
revokeBatch(sessionIds: SessionId[]): Promise<Result<number>>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @auth-craft/tenant-access-control-dynamodb
|
|
60
|
+
*
|
|
61
|
+
* DynamoDB implementation for tenant-access-control.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* import { createTenantAccessSDK } from '@auth-craft/tenant-access-control';
|
|
66
|
+
* import { createTenantAccessDynamoDBPlugin } from '@auth-craft/tenant-access-control-dynamodb';
|
|
67
|
+
*
|
|
68
|
+
* // Create DynamoDB plugin
|
|
69
|
+
* const plugin = createTenantAccessDynamoDBPlugin({
|
|
70
|
+
* tableName: 'auth-table',
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Create SDK with DynamoDB implementation
|
|
74
|
+
* const sdk = createTenantAccessSDK(plugin);
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Create DynamoDB plugin for Tenant Access Control
|
|
80
|
+
*
|
|
81
|
+
* @param config - DynamoDB configuration
|
|
82
|
+
* @returns TenantAccessControlDeps - Dependencies for createTenantAccessSDK
|
|
83
|
+
*/
|
|
84
|
+
declare function createTenantAccessDynamoDBPlugin(config: TenantAccessDynamoDBConfig): TenantAccessControlDeps;
|
|
85
|
+
|
|
86
|
+
export { DynamoDBSessionRepository, DynamoDBTenantMemberRepository, type TenantAccessDynamoDBConfig, createTenantAccessDynamoDBPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { DynamoDBDocumentClient, GetCommand, PutCommand, UpdateCommand, DeleteCommand, QueryCommand, TransactWriteCommand } from '@aws-sdk/lib-dynamodb';
|
|
3
|
+
import { ok, err } from 'ts-micro-result';
|
|
4
|
+
import { tenantAccessErrors } from '@auth-craft/tenant-access-control';
|
|
5
|
+
import { TableAttr, KeyPattern, KeyExtractor, millisToDate, GSIKeys, GSIName } from '@auth-craft/database-plugin-dynamodb';
|
|
6
|
+
|
|
7
|
+
// src/index.ts
|
|
8
|
+
var DynamoDBTenantMemberRepository = class {
|
|
9
|
+
constructor(client, tableName) {
|
|
10
|
+
this.client = client;
|
|
11
|
+
this.tableName = tableName;
|
|
12
|
+
this.docClient = DynamoDBDocumentClient.from(client, {
|
|
13
|
+
marshallOptions: { removeUndefinedValues: true }
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
docClient;
|
|
17
|
+
async get(tenantId, audience, userId) {
|
|
18
|
+
try {
|
|
19
|
+
const response = await this.docClient.send(
|
|
20
|
+
new GetCommand({
|
|
21
|
+
TableName: this.tableName,
|
|
22
|
+
Key: {
|
|
23
|
+
[TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
24
|
+
[TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
if (!response.Item) {
|
|
29
|
+
return ok(null);
|
|
30
|
+
}
|
|
31
|
+
return ok(this.mapItemToEntity(response.Item));
|
|
32
|
+
} catch {
|
|
33
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async create(data) {
|
|
37
|
+
try {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
await this.docClient.send(
|
|
40
|
+
new PutCommand({
|
|
41
|
+
TableName: this.tableName,
|
|
42
|
+
Item: {
|
|
43
|
+
[TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(data.tenantId),
|
|
44
|
+
[TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(data.audience, data.userId),
|
|
45
|
+
userStatus: data.status,
|
|
46
|
+
roleIds: JSON.stringify(data.roleIds),
|
|
47
|
+
permMask: data.permMask ?? 0,
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now
|
|
50
|
+
},
|
|
51
|
+
ConditionExpression: "attribute_not_exists(PK)"
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
return ok();
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error.name === "ConditionalCheckFailedException") {
|
|
57
|
+
return err(tenantAccessErrors.MEMBER_ALREADY_EXISTS());
|
|
58
|
+
}
|
|
59
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async updatePermissions(tenantId, audience, userId, updates) {
|
|
63
|
+
try {
|
|
64
|
+
const setClauses = ["#updatedAt = :updatedAt"];
|
|
65
|
+
const names = { "#updatedAt": "updatedAt" };
|
|
66
|
+
const values = { ":updatedAt": Date.now() };
|
|
67
|
+
if (updates.roleIds !== void 0) {
|
|
68
|
+
setClauses.push("#roleIds = :roleIds");
|
|
69
|
+
names["#roleIds"] = "roleIds";
|
|
70
|
+
values[":roleIds"] = JSON.stringify(updates.roleIds);
|
|
71
|
+
}
|
|
72
|
+
if (updates.permMask !== void 0) {
|
|
73
|
+
setClauses.push("#permMask = :permMask");
|
|
74
|
+
names["#permMask"] = "permMask";
|
|
75
|
+
values[":permMask"] = updates.permMask;
|
|
76
|
+
}
|
|
77
|
+
await this.docClient.send(
|
|
78
|
+
new UpdateCommand({
|
|
79
|
+
TableName: this.tableName,
|
|
80
|
+
Key: {
|
|
81
|
+
[TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
82
|
+
[TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
83
|
+
},
|
|
84
|
+
UpdateExpression: `SET ${setClauses.join(", ")}`,
|
|
85
|
+
ExpressionAttributeNames: names,
|
|
86
|
+
ExpressionAttributeValues: values,
|
|
87
|
+
ConditionExpression: "attribute_exists(PK)"
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
return ok();
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error.name === "ConditionalCheckFailedException") {
|
|
93
|
+
return err(tenantAccessErrors.MEMBER_NOT_FOUND());
|
|
94
|
+
}
|
|
95
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async updateStatus(tenantId, audience, userId, status) {
|
|
99
|
+
try {
|
|
100
|
+
await this.docClient.send(
|
|
101
|
+
new UpdateCommand({
|
|
102
|
+
TableName: this.tableName,
|
|
103
|
+
Key: {
|
|
104
|
+
[TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
105
|
+
[TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
106
|
+
},
|
|
107
|
+
UpdateExpression: "SET #status = :status, #updatedAt = :updatedAt",
|
|
108
|
+
ExpressionAttributeNames: {
|
|
109
|
+
"#status": "userStatus",
|
|
110
|
+
"#updatedAt": "updatedAt"
|
|
111
|
+
},
|
|
112
|
+
ExpressionAttributeValues: {
|
|
113
|
+
":status": status,
|
|
114
|
+
":updatedAt": Date.now()
|
|
115
|
+
},
|
|
116
|
+
ConditionExpression: "attribute_exists(PK)"
|
|
117
|
+
})
|
|
118
|
+
);
|
|
119
|
+
return ok();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error.name === "ConditionalCheckFailedException") {
|
|
122
|
+
return err(tenantAccessErrors.MEMBER_NOT_FOUND());
|
|
123
|
+
}
|
|
124
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async remove(tenantId, audience, userId) {
|
|
128
|
+
try {
|
|
129
|
+
await this.docClient.send(
|
|
130
|
+
new DeleteCommand({
|
|
131
|
+
TableName: this.tableName,
|
|
132
|
+
Key: {
|
|
133
|
+
[TableAttr.PK]: KeyPattern.TENANT_MEMBER_PK(tenantId),
|
|
134
|
+
[TableAttr.SK]: KeyPattern.TENANT_MEMBER_SK(audience, userId)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
return ok();
|
|
139
|
+
} catch {
|
|
140
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
mapItemToEntity(item) {
|
|
144
|
+
const tenantId = KeyExtractor.tenantId(item[TableAttr.PK]);
|
|
145
|
+
const skInfo = KeyExtractor.tenantMemberSKInfo(item[TableAttr.SK]);
|
|
146
|
+
return {
|
|
147
|
+
tenantId,
|
|
148
|
+
audience: skInfo?.audience ?? "",
|
|
149
|
+
userId: skInfo?.userId ?? "",
|
|
150
|
+
status: item["userStatus"],
|
|
151
|
+
roleIds: JSON.parse(item["roleIds"]),
|
|
152
|
+
permMask: item["permMask"] ?? 0,
|
|
153
|
+
createdAt: millisToDate(item["createdAt"]),
|
|
154
|
+
updatedAt: millisToDate(item["updatedAt"])
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var DynamoDBSessionRepository = class {
|
|
159
|
+
constructor(client, tableName) {
|
|
160
|
+
this.client = client;
|
|
161
|
+
this.tableName = tableName;
|
|
162
|
+
this.docClient = DynamoDBDocumentClient.from(client);
|
|
163
|
+
}
|
|
164
|
+
docClient;
|
|
165
|
+
async findActiveByUserId(userId, options) {
|
|
166
|
+
try {
|
|
167
|
+
const limit = Math.min(options?.limit ?? 50, 100);
|
|
168
|
+
let exclusiveStartKey;
|
|
169
|
+
if (options?.cursor) {
|
|
170
|
+
try {
|
|
171
|
+
exclusiveStartKey = JSON.parse(
|
|
172
|
+
Buffer.from(options.cursor, "base64url").toString("utf-8")
|
|
173
|
+
);
|
|
174
|
+
} catch {
|
|
175
|
+
return err(tenantAccessErrors.INVALID_CURSOR());
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const response = await this.docClient.send(
|
|
179
|
+
new QueryCommand({
|
|
180
|
+
TableName: this.tableName,
|
|
181
|
+
IndexName: GSIName.ACTIVE_SESSIONS,
|
|
182
|
+
KeyConditionExpression: `${TableAttr.ACTIVE_SESSION_PK} = :activePk`,
|
|
183
|
+
ExpressionAttributeValues: {
|
|
184
|
+
":activePk": GSIKeys.ACTIVE_SESSION_PK(userId)
|
|
185
|
+
},
|
|
186
|
+
ProjectionExpression: `${TableAttr.PK}, ${TableAttr.TENANT_ID}, ${TableAttr.AUDIENCE}, ${TableAttr.USER_ID}`,
|
|
187
|
+
Limit: limit,
|
|
188
|
+
ExclusiveStartKey: exclusiveStartKey,
|
|
189
|
+
ScanIndexForward: false
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
const sessions = (response.Items ?? []).map((item) => ({
|
|
193
|
+
id: KeyExtractor.sessionId(item[TableAttr.PK]),
|
|
194
|
+
userId: item[TableAttr.USER_ID],
|
|
195
|
+
tenantId: item[TableAttr.TENANT_ID],
|
|
196
|
+
audience: item[TableAttr.AUDIENCE]
|
|
197
|
+
}));
|
|
198
|
+
const hasNext = !!response.LastEvaluatedKey;
|
|
199
|
+
const cursor = hasNext ? Buffer.from(JSON.stringify(response.LastEvaluatedKey)).toString("base64url") : null;
|
|
200
|
+
const pagination = {
|
|
201
|
+
type: "cursor",
|
|
202
|
+
limit,
|
|
203
|
+
count: sessions.length,
|
|
204
|
+
hasNext,
|
|
205
|
+
hasPrev: !!options?.cursor,
|
|
206
|
+
cursor
|
|
207
|
+
};
|
|
208
|
+
return ok(sessions, { pagination });
|
|
209
|
+
} catch {
|
|
210
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async revokeBatch(sessionIds) {
|
|
214
|
+
if (sessionIds.length === 0) {
|
|
215
|
+
return ok(0);
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
const revokedAt = Date.now();
|
|
219
|
+
let count = 0;
|
|
220
|
+
const batchSize = 25;
|
|
221
|
+
for (let i = 0; i < sessionIds.length; i += batchSize) {
|
|
222
|
+
const batch = sessionIds.slice(i, i + batchSize);
|
|
223
|
+
const transactItems = batch.map((sessionId) => ({
|
|
224
|
+
Update: {
|
|
225
|
+
TableName: this.tableName,
|
|
226
|
+
Key: {
|
|
227
|
+
[TableAttr.PK]: KeyPattern.SESSION_PK(sessionId),
|
|
228
|
+
[TableAttr.SK]: KeyPattern.SESSION_SK()
|
|
229
|
+
},
|
|
230
|
+
UpdateExpression: `SET #revokedAt = :revokedAt REMOVE #activePk, #activeSk`,
|
|
231
|
+
ExpressionAttributeNames: {
|
|
232
|
+
"#revokedAt": TableAttr.REVOKED_AT,
|
|
233
|
+
"#activePk": TableAttr.ACTIVE_SESSION_PK,
|
|
234
|
+
"#activeSk": TableAttr.ACTIVE_SESSION_SK
|
|
235
|
+
},
|
|
236
|
+
ExpressionAttributeValues: {
|
|
237
|
+
":revokedAt": revokedAt
|
|
238
|
+
},
|
|
239
|
+
ConditionExpression: `attribute_exists(${TableAttr.PK}) AND attribute_not_exists(#revokedAt)`
|
|
240
|
+
}
|
|
241
|
+
}));
|
|
242
|
+
try {
|
|
243
|
+
await this.docClient.send(
|
|
244
|
+
new TransactWriteCommand({ TransactItems: transactItems })
|
|
245
|
+
);
|
|
246
|
+
count += batch.length;
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return ok(count);
|
|
251
|
+
} catch {
|
|
252
|
+
return err(tenantAccessErrors.DATABASE_ERROR());
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// src/index.ts
|
|
258
|
+
function createTenantAccessDynamoDBPlugin(config) {
|
|
259
|
+
const client = config.client ?? new DynamoDBClient({});
|
|
260
|
+
return {
|
|
261
|
+
tenantMemberRepository: new DynamoDBTenantMemberRepository(client, config.tableName),
|
|
262
|
+
sessionRepository: new DynamoDBSessionRepository(client, config.tableName)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { DynamoDBSessionRepository, DynamoDBTenantMemberRepository, createTenantAccessDynamoDBPlugin };
|
|
267
|
+
//# sourceMappingURL=index.js.map
|
|
268
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dynamodb-tenant-member-repo.ts","../src/dynamodb-session-repo.ts","../src/index.ts"],"names":["DynamoDBDocumentClient","err","tenantAccessErrors","TableAttr","KeyExtractor","ok","KeyPattern","DynamoDBClient"],"mappings":"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","file":"index.js","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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auth-craft/tenant-access-control-dynamodb",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "DynamoDB implementation for tenant-access-control",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"type-check": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@auth-craft/tenant-access-control": "workspace:*",
|
|
27
|
+
"@auth-craft/database-plugin-dynamodb": "workspace:*",
|
|
28
|
+
"@aws-sdk/client-dynamodb": "^3.600.0",
|
|
29
|
+
"@aws-sdk/lib-dynamodb": "^3.600.0",
|
|
30
|
+
"ts-micro-result": "^2.2.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@auth-craft/tsup-config": "workspace:*",
|
|
34
|
+
"@types/node": "^22.10.0",
|
|
35
|
+
"tsup": "^8.3.5",
|
|
36
|
+
"typescript": "^5.7.2"
|
|
37
|
+
}
|
|
38
|
+
}
|