@pikku/kysely 0.12.5 → 0.12.7
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/CHANGELOG.md +15 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/kysely-agent-run-service.js +59 -59
- package/dist/src/kysely-ai-storage-service.js +174 -174
- package/dist/src/kysely-channel-store.js +12 -12
- package/dist/src/kysely-credential-service.d.ts +30 -0
- package/dist/src/kysely-credential-service.js +188 -0
- package/dist/src/kysely-deployment-service.js +22 -22
- package/dist/src/kysely-eventhub-store.js +8 -8
- package/dist/src/kysely-secret-service.js +19 -19
- package/dist/src/kysely-tables.d.ts +100 -82
- package/dist/src/kysely-workflow-run-service.js +73 -73
- package/dist/src/kysely-workflow-service.js +127 -127
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/kysely-agent-run-service.ts +59 -59
- package/src/kysely-ai-storage-service.ts +174 -174
- package/src/kysely-channel-store.ts +12 -12
- package/src/kysely-credential-service.ts +252 -0
- package/src/kysely-deployment-service.ts +24 -24
- package/src/kysely-eventhub-store.ts +8 -8
- package/src/kysely-secret-service.ts +19 -19
- package/src/kysely-services.test.ts +67 -8
- package/src/kysely-tables.ts +102 -82
- package/src/kysely-workflow-run-service.ts +76 -76
- package/src/kysely-workflow-service.ts +129 -129
|
@@ -38,9 +38,9 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
38
38
|
await this.db
|
|
39
39
|
.insertInto('channels')
|
|
40
40
|
.values({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
channelId: channelId,
|
|
42
|
+
channelName: channelName,
|
|
43
|
+
openingData: JSON.stringify(openingData || {}),
|
|
44
44
|
})
|
|
45
45
|
.execute();
|
|
46
46
|
}
|
|
@@ -50,30 +50,30 @@ export class KyselyChannelStore extends ChannelStore {
|
|
|
50
50
|
}
|
|
51
51
|
await this.db
|
|
52
52
|
.deleteFrom('channels')
|
|
53
|
-
.where('
|
|
53
|
+
.where('channelId', 'in', channelIds)
|
|
54
54
|
.execute();
|
|
55
55
|
}
|
|
56
56
|
async setUserSession(channelId, session) {
|
|
57
57
|
await this.db
|
|
58
58
|
.updateTable('channels')
|
|
59
|
-
.set({
|
|
60
|
-
.where('
|
|
59
|
+
.set({ userSession: session ? JSON.stringify(session) : null })
|
|
60
|
+
.where('channelId', '=', channelId)
|
|
61
61
|
.execute();
|
|
62
62
|
}
|
|
63
63
|
async getChannelAndSession(channelId) {
|
|
64
64
|
const row = await this.db
|
|
65
65
|
.selectFrom('channels')
|
|
66
|
-
.select(['
|
|
67
|
-
.where('
|
|
66
|
+
.select(['channelId', 'channelName', 'openingData', 'userSession'])
|
|
67
|
+
.where('channelId', '=', channelId)
|
|
68
68
|
.executeTakeFirst();
|
|
69
69
|
if (!row) {
|
|
70
70
|
throw new Error(`Channel not found: ${channelId}`);
|
|
71
71
|
}
|
|
72
72
|
return {
|
|
73
|
-
channelId: row.
|
|
74
|
-
channelName: row.
|
|
75
|
-
openingData: parseJson(row.
|
|
76
|
-
session: (parseJson(row.
|
|
73
|
+
channelId: row.channelId,
|
|
74
|
+
channelName: row.channelName,
|
|
75
|
+
openingData: parseJson(row.openingData) ?? {},
|
|
76
|
+
session: (parseJson(row.userSession) ?? {}),
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
async close() { }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CredentialService } from '@pikku/core/services';
|
|
2
|
+
import type { Kysely } from 'kysely';
|
|
3
|
+
import type { KyselyPikkuDB } from './kysely-tables.js';
|
|
4
|
+
export interface KyselyCredentialServiceConfig {
|
|
5
|
+
key: string;
|
|
6
|
+
keyVersion?: number;
|
|
7
|
+
previousKey?: string;
|
|
8
|
+
audit?: boolean;
|
|
9
|
+
auditReads?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class KyselyCredentialService implements CredentialService {
|
|
12
|
+
private db;
|
|
13
|
+
private initialized;
|
|
14
|
+
private key;
|
|
15
|
+
private keyVersion;
|
|
16
|
+
private previousKey?;
|
|
17
|
+
private audit;
|
|
18
|
+
private auditReads;
|
|
19
|
+
constructor(db: Kysely<KyselyPikkuDB>, config: KyselyCredentialServiceConfig);
|
|
20
|
+
init(): Promise<void>;
|
|
21
|
+
private logAudit;
|
|
22
|
+
private getKEK;
|
|
23
|
+
private whereUserId;
|
|
24
|
+
get<T = unknown>(name: string, userId?: string): Promise<T | null>;
|
|
25
|
+
set(name: string, value: unknown, userId?: string): Promise<void>;
|
|
26
|
+
delete(name: string, userId?: string): Promise<void>;
|
|
27
|
+
has(name: string, userId?: string): Promise<boolean>;
|
|
28
|
+
getAll(userId: string): Promise<Record<string, unknown>>;
|
|
29
|
+
rotateKEK(): Promise<number>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { sql } from 'kysely';
|
|
2
|
+
import { envelopeEncrypt, envelopeDecrypt, envelopeRewrap, } from '@pikku/core/crypto-utils';
|
|
3
|
+
export class KyselyCredentialService {
|
|
4
|
+
db;
|
|
5
|
+
initialized = false;
|
|
6
|
+
key;
|
|
7
|
+
keyVersion;
|
|
8
|
+
previousKey;
|
|
9
|
+
audit;
|
|
10
|
+
auditReads;
|
|
11
|
+
constructor(db, config) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.key = config.key;
|
|
14
|
+
this.keyVersion = config.keyVersion ?? 1;
|
|
15
|
+
this.previousKey = config.previousKey;
|
|
16
|
+
this.audit = config.audit ?? false;
|
|
17
|
+
this.auditReads = config.auditReads ?? false;
|
|
18
|
+
}
|
|
19
|
+
async init() {
|
|
20
|
+
if (this.initialized)
|
|
21
|
+
return;
|
|
22
|
+
await this.db.schema
|
|
23
|
+
.createTable('credentials')
|
|
24
|
+
.ifNotExists()
|
|
25
|
+
.addColumn('name', 'varchar(255)', (col) => col.notNull())
|
|
26
|
+
.addColumn('user_id', 'varchar(255)')
|
|
27
|
+
.addColumn('ciphertext', 'text', (col) => col.notNull())
|
|
28
|
+
.addColumn('wrapped_dek', 'text', (col) => col.notNull())
|
|
29
|
+
.addColumn('key_version', 'integer', (col) => col.notNull())
|
|
30
|
+
.addColumn('created_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
31
|
+
.addColumn('updated_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
32
|
+
.execute();
|
|
33
|
+
await sql `CREATE UNIQUE INDEX IF NOT EXISTS credentials_name_user_id_unique ON credentials (name, COALESCE(user_id, ''))`.execute(this.db);
|
|
34
|
+
if (this.audit) {
|
|
35
|
+
await this.db.schema
|
|
36
|
+
.createTable('credentials_audit')
|
|
37
|
+
.ifNotExists()
|
|
38
|
+
.addColumn('id', 'varchar(36)', (col) => col.primaryKey())
|
|
39
|
+
.addColumn('credential_name', 'varchar(255)', (col) => col.notNull())
|
|
40
|
+
.addColumn('user_id', 'varchar(255)')
|
|
41
|
+
.addColumn('action', 'varchar(20)', (col) => col.notNull())
|
|
42
|
+
.addColumn('performed_at', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
|
|
43
|
+
.execute();
|
|
44
|
+
}
|
|
45
|
+
this.initialized = true;
|
|
46
|
+
}
|
|
47
|
+
async logAudit(name, userId, action) {
|
|
48
|
+
if (!this.audit)
|
|
49
|
+
return;
|
|
50
|
+
if (action === 'read' && !this.auditReads)
|
|
51
|
+
return;
|
|
52
|
+
await this.db
|
|
53
|
+
.insertInto('credentialsAudit')
|
|
54
|
+
.values({
|
|
55
|
+
id: crypto.randomUUID(),
|
|
56
|
+
credentialName: name,
|
|
57
|
+
userId: userId ?? null,
|
|
58
|
+
action,
|
|
59
|
+
performedAt: new Date().toISOString(),
|
|
60
|
+
})
|
|
61
|
+
.execute();
|
|
62
|
+
}
|
|
63
|
+
getKEK(version) {
|
|
64
|
+
if (version === this.keyVersion)
|
|
65
|
+
return this.key;
|
|
66
|
+
if (this.previousKey)
|
|
67
|
+
return this.previousKey;
|
|
68
|
+
throw new Error(`No KEK available for key_version ${version}`);
|
|
69
|
+
}
|
|
70
|
+
whereUserId(qb, userId) {
|
|
71
|
+
return userId
|
|
72
|
+
? qb.where('userId', '=', userId)
|
|
73
|
+
: qb.where('userId', 'is', null);
|
|
74
|
+
}
|
|
75
|
+
async get(name, userId) {
|
|
76
|
+
let qb = this.db
|
|
77
|
+
.selectFrom('credentials')
|
|
78
|
+
.select(['ciphertext', 'wrappedDek', 'keyVersion'])
|
|
79
|
+
.where('name', '=', name);
|
|
80
|
+
qb = this.whereUserId(qb, userId);
|
|
81
|
+
const row = await qb.executeTakeFirst();
|
|
82
|
+
if (!row)
|
|
83
|
+
return null;
|
|
84
|
+
const kek = this.getKEK(row.keyVersion);
|
|
85
|
+
const plaintext = await envelopeDecrypt(kek, row.ciphertext, row.wrappedDek);
|
|
86
|
+
await this.logAudit(name, userId, 'read');
|
|
87
|
+
try {
|
|
88
|
+
return JSON.parse(plaintext);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
throw new Error(`Credential '${name}' contains invalid data`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async set(name, value, userId) {
|
|
95
|
+
const plaintext = JSON.stringify(value);
|
|
96
|
+
const { ciphertext, wrappedDEK } = await envelopeEncrypt(this.key, plaintext);
|
|
97
|
+
const now = new Date().toISOString();
|
|
98
|
+
const exists = await this.has(name, userId);
|
|
99
|
+
if (exists) {
|
|
100
|
+
let qb = this.db
|
|
101
|
+
.updateTable('credentials')
|
|
102
|
+
.set({
|
|
103
|
+
ciphertext,
|
|
104
|
+
wrappedDek: wrappedDEK,
|
|
105
|
+
keyVersion: this.keyVersion,
|
|
106
|
+
updatedAt: now,
|
|
107
|
+
})
|
|
108
|
+
.where('name', '=', name);
|
|
109
|
+
qb = this.whereUserId(qb, userId);
|
|
110
|
+
await qb.execute();
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
await this.db
|
|
114
|
+
.insertInto('credentials')
|
|
115
|
+
.values({
|
|
116
|
+
name,
|
|
117
|
+
userId: userId ?? null,
|
|
118
|
+
ciphertext,
|
|
119
|
+
wrappedDek: wrappedDEK,
|
|
120
|
+
keyVersion: this.keyVersion,
|
|
121
|
+
createdAt: now,
|
|
122
|
+
updatedAt: now,
|
|
123
|
+
})
|
|
124
|
+
.execute();
|
|
125
|
+
}
|
|
126
|
+
await this.logAudit(name, userId, 'write');
|
|
127
|
+
}
|
|
128
|
+
async delete(name, userId) {
|
|
129
|
+
let qb = this.db.deleteFrom('credentials').where('name', '=', name);
|
|
130
|
+
qb = this.whereUserId(qb, userId);
|
|
131
|
+
await qb.execute();
|
|
132
|
+
await this.logAudit(name, userId, 'delete');
|
|
133
|
+
}
|
|
134
|
+
async has(name, userId) {
|
|
135
|
+
let qb = this.db
|
|
136
|
+
.selectFrom('credentials')
|
|
137
|
+
.select('name')
|
|
138
|
+
.where('name', '=', name);
|
|
139
|
+
qb = this.whereUserId(qb, userId);
|
|
140
|
+
const row = await qb.executeTakeFirst();
|
|
141
|
+
return !!row;
|
|
142
|
+
}
|
|
143
|
+
async getAll(userId) {
|
|
144
|
+
const rows = await this.db
|
|
145
|
+
.selectFrom('credentials')
|
|
146
|
+
.select(['name', 'ciphertext', 'wrappedDek', 'keyVersion'])
|
|
147
|
+
.where('userId', '=', userId)
|
|
148
|
+
.execute();
|
|
149
|
+
const result = {};
|
|
150
|
+
for (const row of rows) {
|
|
151
|
+
const kek = this.getKEK(row.keyVersion);
|
|
152
|
+
const plaintext = await envelopeDecrypt(kek, row.ciphertext, row.wrappedDek);
|
|
153
|
+
try {
|
|
154
|
+
result[row.name] = JSON.parse(plaintext);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new Error(`Credential '${row.name}' contains invalid data`);
|
|
158
|
+
}
|
|
159
|
+
await this.logAudit(row.name, userId, 'read');
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
async rotateKEK() {
|
|
164
|
+
if (!this.previousKey) {
|
|
165
|
+
throw new Error('No previousKey configured — nothing to rotate from');
|
|
166
|
+
}
|
|
167
|
+
const rows = await this.db
|
|
168
|
+
.selectFrom('credentials')
|
|
169
|
+
.select(['name', 'userId', 'wrappedDek'])
|
|
170
|
+
.where('keyVersion', '<', this.keyVersion)
|
|
171
|
+
.execute();
|
|
172
|
+
for (const row of rows) {
|
|
173
|
+
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.wrappedDek);
|
|
174
|
+
let qb = this.db
|
|
175
|
+
.updateTable('credentials')
|
|
176
|
+
.set({
|
|
177
|
+
wrappedDek: newWrappedDEK,
|
|
178
|
+
keyVersion: this.keyVersion,
|
|
179
|
+
updatedAt: new Date().toISOString(),
|
|
180
|
+
})
|
|
181
|
+
.where('name', '=', row.name);
|
|
182
|
+
qb = this.whereUserId(qb, row.userId ?? undefined);
|
|
183
|
+
await qb.execute();
|
|
184
|
+
await this.logAudit(row.name, row.userId ?? undefined, 'rotate');
|
|
185
|
+
}
|
|
186
|
+
return rows.length;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -54,27 +54,27 @@ export class KyselyDeploymentService {
|
|
|
54
54
|
this.deploymentConfig = { ...config, functions };
|
|
55
55
|
await this.db.transaction().execute(async (trx) => {
|
|
56
56
|
await trx
|
|
57
|
-
.insertInto('
|
|
57
|
+
.insertInto('pikkuDeployments')
|
|
58
58
|
.values({
|
|
59
|
-
|
|
59
|
+
deploymentId: config.deploymentId,
|
|
60
60
|
endpoint: config.endpoint,
|
|
61
|
-
|
|
61
|
+
lastHeartbeat: new Date(),
|
|
62
62
|
})
|
|
63
|
-
.onConflict((oc) => oc.column('
|
|
63
|
+
.onConflict((oc) => oc.column('deploymentId').doUpdateSet({
|
|
64
64
|
endpoint: config.endpoint,
|
|
65
|
-
|
|
65
|
+
lastHeartbeat: new Date(),
|
|
66
66
|
}))
|
|
67
67
|
.execute();
|
|
68
68
|
await trx
|
|
69
|
-
.deleteFrom('
|
|
70
|
-
.where('
|
|
69
|
+
.deleteFrom('pikkuDeploymentFunctions')
|
|
70
|
+
.where('deploymentId', '=', config.deploymentId)
|
|
71
71
|
.execute();
|
|
72
72
|
if (functions.length > 0) {
|
|
73
73
|
await trx
|
|
74
|
-
.insertInto('
|
|
74
|
+
.insertInto('pikkuDeploymentFunctions')
|
|
75
75
|
.values(functions.map((fn) => ({
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
deploymentId: config.deploymentId,
|
|
77
|
+
functionName: fn,
|
|
78
78
|
})))
|
|
79
79
|
.execute();
|
|
80
80
|
}
|
|
@@ -88,8 +88,8 @@ export class KyselyDeploymentService {
|
|
|
88
88
|
}
|
|
89
89
|
if (this.deploymentConfig) {
|
|
90
90
|
await this.db
|
|
91
|
-
.deleteFrom('
|
|
92
|
-
.where('
|
|
91
|
+
.deleteFrom('pikkuDeployments')
|
|
92
|
+
.where('deploymentId', '=', this.deploymentConfig.deploymentId)
|
|
93
93
|
.execute();
|
|
94
94
|
}
|
|
95
95
|
}
|
|
@@ -97,15 +97,15 @@ export class KyselyDeploymentService {
|
|
|
97
97
|
const ttlMs = this.heartbeatTtl;
|
|
98
98
|
const cutoff = new Date(Date.now() - ttlMs);
|
|
99
99
|
const result = await this.db
|
|
100
|
-
.selectFrom('
|
|
101
|
-
.innerJoin('
|
|
102
|
-
.select(['d.
|
|
103
|
-
.where('f.
|
|
104
|
-
.where('d.
|
|
105
|
-
.orderBy('d.
|
|
100
|
+
.selectFrom('pikkuDeployments as d')
|
|
101
|
+
.innerJoin('pikkuDeploymentFunctions as f', 'f.deploymentId', 'd.deploymentId')
|
|
102
|
+
.select(['d.deploymentId', 'd.endpoint'])
|
|
103
|
+
.where('f.functionName', '=', name)
|
|
104
|
+
.where('d.lastHeartbeat', '>', cutoff)
|
|
105
|
+
.orderBy('d.lastHeartbeat', 'desc')
|
|
106
106
|
.execute();
|
|
107
107
|
return result.map((row) => ({
|
|
108
|
-
deploymentId: row.
|
|
108
|
+
deploymentId: row.deploymentId,
|
|
109
109
|
endpoint: row.endpoint,
|
|
110
110
|
}));
|
|
111
111
|
}
|
|
@@ -128,9 +128,9 @@ export class KyselyDeploymentService {
|
|
|
128
128
|
return;
|
|
129
129
|
try {
|
|
130
130
|
await this.db
|
|
131
|
-
.updateTable('
|
|
132
|
-
.set({
|
|
133
|
-
.where('
|
|
131
|
+
.updateTable('pikkuDeployments')
|
|
132
|
+
.set({ lastHeartbeat: new Date() })
|
|
133
|
+
.where('deploymentId', '=', this.deploymentConfig.deploymentId)
|
|
134
134
|
.execute();
|
|
135
135
|
}
|
|
136
136
|
catch {
|
|
@@ -14,18 +14,18 @@ export class KyselyEventHubStore extends EventHubStore {
|
|
|
14
14
|
}
|
|
15
15
|
async getChannelIdsForTopic(topic) {
|
|
16
16
|
const result = await this.db
|
|
17
|
-
.selectFrom('
|
|
18
|
-
.select('
|
|
17
|
+
.selectFrom('channelSubscriptions')
|
|
18
|
+
.select('channelId')
|
|
19
19
|
.where('topic', '=', topic)
|
|
20
20
|
.execute();
|
|
21
|
-
return result.map((row) => row.
|
|
21
|
+
return result.map((row) => row.channelId);
|
|
22
22
|
}
|
|
23
23
|
async subscribe(topic, channelId) {
|
|
24
24
|
try {
|
|
25
25
|
await this.db
|
|
26
|
-
.insertInto('
|
|
27
|
-
.values({
|
|
28
|
-
.onConflict((oc) => oc.columns(['
|
|
26
|
+
.insertInto('channelSubscriptions')
|
|
27
|
+
.values({ channelId: channelId, topic: topic })
|
|
28
|
+
.onConflict((oc) => oc.columns(['channelId', 'topic']).doNothing())
|
|
29
29
|
.execute();
|
|
30
30
|
return true;
|
|
31
31
|
}
|
|
@@ -35,8 +35,8 @@ export class KyselyEventHubStore extends EventHubStore {
|
|
|
35
35
|
}
|
|
36
36
|
async unsubscribe(topic, channelId) {
|
|
37
37
|
const result = await this.db
|
|
38
|
-
.deleteFrom('
|
|
39
|
-
.where('
|
|
38
|
+
.deleteFrom('channelSubscriptions')
|
|
39
|
+
.where('channelId', '=', channelId)
|
|
40
40
|
.where('topic', '=', topic)
|
|
41
41
|
.executeTakeFirst();
|
|
42
42
|
return BigInt(result.numDeletedRows) > 0n;
|
|
@@ -47,12 +47,12 @@ export class KyselySecretService {
|
|
|
47
47
|
if (action === 'read' && !this.auditReads)
|
|
48
48
|
return;
|
|
49
49
|
await this.db
|
|
50
|
-
.insertInto('
|
|
50
|
+
.insertInto('secretsAudit')
|
|
51
51
|
.values({
|
|
52
52
|
id: crypto.randomUUID(),
|
|
53
|
-
|
|
53
|
+
secretKey: secretKey,
|
|
54
54
|
action,
|
|
55
|
-
|
|
55
|
+
performedAt: new Date().toISOString(),
|
|
56
56
|
})
|
|
57
57
|
.execute();
|
|
58
58
|
}
|
|
@@ -66,13 +66,13 @@ export class KyselySecretService {
|
|
|
66
66
|
async getSecret(key) {
|
|
67
67
|
const row = await this.db
|
|
68
68
|
.selectFrom('secrets')
|
|
69
|
-
.select(['ciphertext', '
|
|
69
|
+
.select(['ciphertext', 'wrappedDek', 'keyVersion'])
|
|
70
70
|
.where('key', '=', key)
|
|
71
71
|
.executeTakeFirst();
|
|
72
72
|
if (!row)
|
|
73
73
|
throw new Error('Requested secret not found');
|
|
74
|
-
const kek = this.getKEK(row.
|
|
75
|
-
const result = await envelopeDecrypt(kek, row.ciphertext, row.
|
|
74
|
+
const kek = this.getKEK(row.keyVersion);
|
|
75
|
+
const result = await envelopeDecrypt(kek, row.ciphertext, row.wrappedDek);
|
|
76
76
|
await this.logAudit(key, 'read');
|
|
77
77
|
return result;
|
|
78
78
|
}
|
|
@@ -97,16 +97,16 @@ export class KyselySecretService {
|
|
|
97
97
|
.values({
|
|
98
98
|
key,
|
|
99
99
|
ciphertext,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
wrappedDek: wrappedDEK,
|
|
101
|
+
keyVersion: this.keyVersion,
|
|
102
|
+
createdAt: now,
|
|
103
|
+
updatedAt: now,
|
|
104
104
|
})
|
|
105
105
|
.onConflict((oc) => oc.column('key').doUpdateSet({
|
|
106
106
|
ciphertext,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
wrappedDek: wrappedDEK,
|
|
108
|
+
keyVersion: this.keyVersion,
|
|
109
|
+
updatedAt: now,
|
|
110
110
|
}))
|
|
111
111
|
.execute();
|
|
112
112
|
await this.logAudit(key, 'write');
|
|
@@ -121,17 +121,17 @@ export class KyselySecretService {
|
|
|
121
121
|
}
|
|
122
122
|
const rows = await this.db
|
|
123
123
|
.selectFrom('secrets')
|
|
124
|
-
.select(['key', '
|
|
125
|
-
.where('
|
|
124
|
+
.select(['key', 'wrappedDek'])
|
|
125
|
+
.where('keyVersion', '<', this.keyVersion)
|
|
126
126
|
.execute();
|
|
127
127
|
for (const row of rows) {
|
|
128
|
-
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.
|
|
128
|
+
const newWrappedDEK = await envelopeRewrap(this.previousKey, this.key, row.wrappedDek);
|
|
129
129
|
await this.db
|
|
130
130
|
.updateTable('secrets')
|
|
131
131
|
.set({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
wrappedDek: newWrappedDEK,
|
|
133
|
+
keyVersion: this.keyVersion,
|
|
134
|
+
updatedAt: new Date().toISOString(),
|
|
135
135
|
})
|
|
136
136
|
.where('key', '=', row.key)
|
|
137
137
|
.execute();
|