@arcblock/did-connect-service 4.0.5 → 4.0.6
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/assets/fonts/noto-sans-sc-regular.otf +0 -0
- package/dist/embedded.d.ts +32 -0
- package/dist/embedded.d.ts.map +1 -1
- package/dist/embedded.js +3 -0
- package/dist/embedded.js.map +1 -1
- package/dist/handlers/auth-handler.d.ts +5 -0
- package/dist/handlers/auth-handler.d.ts.map +1 -1
- package/dist/handlers/auth-handler.js +1 -22
- package/dist/handlers/auth-handler.js.map +1 -1
- package/dist/handlers/branding-handler.d.ts +17 -0
- package/dist/handlers/branding-handler.d.ts.map +1 -1
- package/dist/handlers/branding-handler.js +107 -5
- package/dist/handlers/branding-handler.js.map +1 -1
- package/dist/identity/gravatar.d.ts +0 -2
- package/dist/identity/gravatar.d.ts.map +1 -1
- package/dist/identity/gravatar.js +0 -9
- package/dist/identity/gravatar.js.map +1 -1
- package/dist/og/emoji.d.ts +12 -0
- package/dist/og/emoji.d.ts.map +1 -0
- package/dist/og/emoji.js +71 -0
- package/dist/og/emoji.js.map +1 -0
- package/dist/og/generator.d.ts +3 -0
- package/dist/og/generator.d.ts.map +1 -0
- package/dist/og/generator.js +338 -0
- package/dist/og/generator.js.map +1 -0
- package/dist/og/index.d.ts +6 -0
- package/dist/og/index.d.ts.map +1 -0
- package/dist/og/index.js +4 -0
- package/dist/og/index.js.map +1 -0
- package/dist/og/passport-svg.d.ts +52 -0
- package/dist/og/passport-svg.d.ts.map +1 -0
- package/dist/og/passport-svg.js +157 -0
- package/dist/og/passport-svg.js.map +1 -0
- package/dist/og/ssrf-guard.d.ts +38 -0
- package/dist/og/ssrf-guard.d.ts.map +1 -0
- package/dist/og/ssrf-guard.js +188 -0
- package/dist/og/ssrf-guard.js.map +1 -0
- package/dist/og/templates.d.ts +26 -0
- package/dist/og/templates.d.ts.map +1 -0
- package/dist/og/templates.js +302 -0
- package/dist/og/templates.js.map +1 -0
- package/dist/og/types.d.ts +74 -0
- package/dist/og/types.d.ts.map +1 -0
- package/dist/og/types.js +14 -0
- package/dist/og/types.js.map +1 -0
- package/package.json +18 -4
- package/dist/access-key-handler.d.ts +0 -37
- package/dist/access-key-handler.d.ts.map +0 -1
- package/dist/access-key-handler.js +0 -316
- package/dist/access-key-handler.js.map +0 -1
- package/dist/access-key-util.d.ts +0 -19
- package/dist/access-key-util.d.ts.map +0 -1
- package/dist/access-key-util.js +0 -45
- package/dist/access-key-util.js.map +0 -1
- package/dist/access-policy.d.ts +0 -53
- package/dist/access-policy.d.ts.map +0 -1
- package/dist/access-policy.js +0 -153
- package/dist/access-policy.js.map +0 -1
- package/dist/auth-client.d.ts +0 -20
- package/dist/auth-client.d.ts.map +0 -1
- package/dist/auth-client.js +0 -42
- package/dist/auth-client.js.map +0 -1
- package/dist/auth-entrypoint.d.ts +0 -45
- package/dist/auth-entrypoint.d.ts.map +0 -1
- package/dist/auth-entrypoint.js +0 -31
- package/dist/auth-entrypoint.js.map +0 -1
- package/dist/auth-handler.d.ts +0 -136
- package/dist/auth-handler.d.ts.map +0 -1
- package/dist/auth-handler.js +0 -408
- package/dist/auth-handler.js.map +0 -1
- package/dist/auth-rpc-types.d.ts +0 -139
- package/dist/auth-rpc-types.d.ts.map +0 -1
- package/dist/auth-rpc-types.js +0 -11
- package/dist/auth-rpc-types.js.map +0 -1
- package/dist/auth-rpc.d.ts +0 -80
- package/dist/auth-rpc.d.ts.map +0 -1
- package/dist/auth-rpc.js +0 -257
- package/dist/auth-rpc.js.map +0 -1
- package/dist/auth-worker.d.ts +0 -42
- package/dist/auth-worker.d.ts.map +0 -1
- package/dist/auth-worker.js +0 -120
- package/dist/auth-worker.js.map +0 -1
- package/dist/blocklet-js-handler.d.ts +0 -22
- package/dist/blocklet-js-handler.d.ts.map +0 -1
- package/dist/blocklet-js-handler.js +0 -205
- package/dist/blocklet-js-handler.js.map +0 -1
- package/dist/branding-handler.d.ts +0 -42
- package/dist/branding-handler.d.ts.map +0 -1
- package/dist/branding-handler.js +0 -326
- package/dist/branding-handler.js.map +0 -1
- package/dist/d1-token-storage.d.ts +0 -31
- package/dist/d1-token-storage.d.ts.map +0 -1
- package/dist/d1-token-storage.js +0 -83
- package/dist/d1-token-storage.js.map +0 -1
- package/dist/did-connect-handler.d.ts +0 -57
- package/dist/did-connect-handler.d.ts.map +0 -1
- package/dist/did-connect-handler.js +0 -182
- package/dist/did-connect-handler.js.map +0 -1
- package/dist/did.d.ts +0 -14
- package/dist/did.d.ts.map +0 -1
- package/dist/did.js +0 -17
- package/dist/did.js.map +0 -1
- package/dist/email-login-handler.d.ts +0 -50
- package/dist/email-login-handler.d.ts.map +0 -1
- package/dist/email-login-handler.js +0 -238
- package/dist/email-login-handler.js.map +0 -1
- package/dist/federation-utils.d.ts +0 -23
- package/dist/federation-utils.d.ts.map +0 -1
- package/dist/federation-utils.js +0 -25
- package/dist/federation-utils.js.map +0 -1
- package/dist/handler.d.ts +0 -90
- package/dist/handler.d.ts.map +0 -1
- package/dist/handler.js +0 -591
- package/dist/handler.js.map +0 -1
- package/dist/identity/invitation-util.d.ts +0 -7
- package/dist/identity/invitation-util.d.ts.map +0 -1
- package/dist/identity/invitation-util.js +0 -66
- package/dist/identity/invitation-util.js.map +0 -1
- package/dist/instance-role.d.ts +0 -10
- package/dist/instance-role.d.ts.map +0 -1
- package/dist/instance-role.js +0 -20
- package/dist/instance-role.js.map +0 -1
- package/dist/jwt.d.ts +0 -7
- package/dist/jwt.d.ts.map +0 -1
- package/dist/jwt.js +0 -72
- package/dist/jwt.js.map +0 -1
- package/dist/login-entry.d.ts +0 -9
- package/dist/login-entry.d.ts.map +0 -1
- package/dist/login-entry.js +0 -9
- package/dist/login-entry.js.map +0 -1
- package/dist/membership-handler.d.ts +0 -27
- package/dist/membership-handler.d.ts.map +0 -1
- package/dist/membership-handler.js +0 -111
- package/dist/membership-handler.js.map +0 -1
- package/dist/oauth-callback-page.d.ts +0 -9
- package/dist/oauth-callback-page.d.ts.map +0 -1
- package/dist/oauth-callback-page.js +0 -31
- package/dist/oauth-callback-page.js.map +0 -1
- package/dist/oauth-handler.d.ts +0 -72
- package/dist/oauth-handler.d.ts.map +0 -1
- package/dist/oauth-handler.js +0 -423
- package/dist/oauth-handler.js.map +0 -1
- package/dist/page.d.ts +0 -33
- package/dist/page.d.ts.map +0 -1
- package/dist/page.js +0 -59
- package/dist/page.js.map +0 -1
- package/dist/pages/auth-script.d.ts +0 -18
- package/dist/pages/auth-script.d.ts.map +0 -1
- package/dist/pages/auth-script.js +0 -185
- package/dist/pages/auth-script.js.map +0 -1
- package/dist/pages/design-tokens.d.ts +0 -86
- package/dist/pages/design-tokens.d.ts.map +0 -1
- package/dist/pages/design-tokens.js +0 -159
- package/dist/pages/design-tokens.js.map +0 -1
- package/dist/pages/did-connect-script.d.ts +0 -16
- package/dist/pages/did-connect-script.d.ts.map +0 -1
- package/dist/pages/did-connect-script.js +0 -105
- package/dist/pages/did-connect-script.js.map +0 -1
- package/dist/pages/shared-styles.d.ts +0 -6
- package/dist/pages/shared-styles.d.ts.map +0 -1
- package/dist/pages/shared-styles.js +0 -109
- package/dist/pages/shared-styles.js.map +0 -1
- package/dist/rbac.d.ts +0 -19
- package/dist/rbac.d.ts.map +0 -1
- package/dist/rbac.js +0 -76
- package/dist/rbac.js.map +0 -1
- package/dist/session-context.d.ts +0 -35
- package/dist/session-context.d.ts.map +0 -1
- package/dist/session-context.js +0 -39
- package/dist/session-context.js.map +0 -1
- package/dist/store.d.ts +0 -222
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -1366
- package/dist/store.js.map +0 -1
- package/dist/team-handler.d.ts +0 -90
- package/dist/team-handler.d.ts.map +0 -1
- package/dist/team-handler.js +0 -1225
- package/dist/team-handler.js.map +0 -1
- package/dist/ticket-handler.d.ts +0 -28
- package/dist/ticket-handler.d.ts.map +0 -1
- package/dist/ticket-handler.js +0 -74
- package/dist/ticket-handler.js.map +0 -1
- package/dist/wallet-identity.d.ts +0 -32
- package/dist/wallet-identity.d.ts.map +0 -1
- package/dist/wallet-identity.js +0 -43
- package/dist/wallet-identity.js.map +0 -1
- package/dist/webauthn.d.ts +0 -65
- package/dist/webauthn.d.ts.map +0 -1
- package/dist/webauthn.js +0 -112
- package/dist/webauthn.js.map +0 -1
package/dist/store.js
DELETED
|
@@ -1,1366 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* D1Store — D1-backed storage for passkey auth + team management.
|
|
3
|
-
*
|
|
4
|
-
* Uses the same schema as blocklet-server (users + connected_accounts tables)
|
|
5
|
-
* to ensure interoperability. Unused fields default to null/empty.
|
|
6
|
-
*/
|
|
7
|
-
import { dbToAccessType } from "./access-policy.js";
|
|
8
|
-
const SCHEMA_SQL = `
|
|
9
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
10
|
-
did TEXT PRIMARY KEY,
|
|
11
|
-
pk TEXT NOT NULL DEFAULT '',
|
|
12
|
-
fullName TEXT,
|
|
13
|
-
email TEXT,
|
|
14
|
-
avatar TEXT,
|
|
15
|
-
role TEXT,
|
|
16
|
-
remark TEXT DEFAULT '',
|
|
17
|
-
sourceProvider TEXT,
|
|
18
|
-
locale TEXT DEFAULT 'en',
|
|
19
|
-
approved INTEGER DEFAULT 1,
|
|
20
|
-
extra TEXT DEFAULT '{}',
|
|
21
|
-
firstLoginAt TEXT,
|
|
22
|
-
lastLoginAt TEXT,
|
|
23
|
-
lastLoginIp TEXT,
|
|
24
|
-
createdAt TEXT,
|
|
25
|
-
updatedAt TEXT,
|
|
26
|
-
sourceAppPid TEXT,
|
|
27
|
-
didSpace TEXT,
|
|
28
|
-
url TEXT DEFAULT '',
|
|
29
|
-
phone TEXT,
|
|
30
|
-
inviter TEXT,
|
|
31
|
-
generation INTEGER DEFAULT 0,
|
|
32
|
-
emailVerified INTEGER DEFAULT 0,
|
|
33
|
-
phoneVerified INTEGER DEFAULT 0,
|
|
34
|
-
passkeyCount INTEGER DEFAULT 0,
|
|
35
|
-
metadata TEXT DEFAULT '{}',
|
|
36
|
-
address TEXT DEFAULT '{}',
|
|
37
|
-
name TEXT,
|
|
38
|
-
createdByAppPid TEXT
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
CREATE TABLE IF NOT EXISTS connected_accounts (
|
|
42
|
-
did TEXT PRIMARY KEY,
|
|
43
|
-
pk TEXT,
|
|
44
|
-
userDid TEXT REFERENCES users(did),
|
|
45
|
-
provider TEXT NOT NULL,
|
|
46
|
-
id TEXT,
|
|
47
|
-
firstLoginAt TEXT,
|
|
48
|
-
lastLoginAt TEXT,
|
|
49
|
-
lastLoginIp TEXT,
|
|
50
|
-
userInfo TEXT,
|
|
51
|
-
extra TEXT,
|
|
52
|
-
counter INTEGER DEFAULT 0
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
CREATE TABLE IF NOT EXISTS challenges (
|
|
56
|
-
id TEXT PRIMARY KEY,
|
|
57
|
-
challenge TEXT NOT NULL,
|
|
58
|
-
invitationId TEXT,
|
|
59
|
-
createdAt TEXT DEFAULT (datetime('now'))
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
CREATE TABLE IF NOT EXISTS invitations (
|
|
63
|
-
id TEXT PRIMARY KEY,
|
|
64
|
-
teamDid TEXT NOT NULL,
|
|
65
|
-
role TEXT NOT NULL,
|
|
66
|
-
remark TEXT DEFAULT '',
|
|
67
|
-
inviterDid TEXT NOT NULL,
|
|
68
|
-
expireAt TEXT NOT NULL,
|
|
69
|
-
maxUses INTEGER DEFAULT 1,
|
|
70
|
-
useCount INTEGER DEFAULT 0,
|
|
71
|
-
status TEXT DEFAULT 'active',
|
|
72
|
-
createdAt TEXT DEFAULT (datetime('now')),
|
|
73
|
-
updatedAt TEXT DEFAULT (datetime('now'))
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
77
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
78
|
-
action TEXT NOT NULL,
|
|
79
|
-
operatorDid TEXT NOT NULL,
|
|
80
|
-
targetDid TEXT,
|
|
81
|
-
metadata TEXT DEFAULT '{}',
|
|
82
|
-
ip TEXT,
|
|
83
|
-
createdAt TEXT DEFAULT (datetime('now'))
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
87
|
-
CREATE INDEX IF NOT EXISTS idx_users_pk ON users(pk);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_users_createdAt ON users(createdAt);
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_connected_accounts_userDid ON connected_accounts(userDid);
|
|
90
|
-
CREATE INDEX IF NOT EXISTS idx_connected_accounts_pk ON connected_accounts(pk);
|
|
91
|
-
CREATE INDEX IF NOT EXISTS idx_invitations_status ON invitations(status);
|
|
92
|
-
CREATE INDEX IF NOT EXISTS idx_invitations_expireAt ON invitations(expireAt);
|
|
93
|
-
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action);
|
|
94
|
-
CREATE INDEX IF NOT EXISTS idx_audit_logs_operatorDid ON audit_logs(operatorDid);
|
|
95
|
-
CREATE INDEX IF NOT EXISTS idx_audit_logs_createdAt ON audit_logs(createdAt);
|
|
96
|
-
|
|
97
|
-
CREATE TABLE IF NOT EXISTS access_policies (
|
|
98
|
-
id TEXT PRIMARY KEY,
|
|
99
|
-
name TEXT NOT NULL,
|
|
100
|
-
description TEXT DEFAULT '',
|
|
101
|
-
roles TEXT,
|
|
102
|
-
reverse INTEGER DEFAULT 0,
|
|
103
|
-
isProtected INTEGER DEFAULT 0,
|
|
104
|
-
createdAt TEXT DEFAULT (datetime('now')),
|
|
105
|
-
updatedAt TEXT DEFAULT (datetime('now'))
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
CREATE TABLE IF NOT EXISTS security_rules (
|
|
109
|
-
id TEXT PRIMARY KEY,
|
|
110
|
-
pathPattern TEXT NOT NULL,
|
|
111
|
-
priority INTEGER DEFAULT 0,
|
|
112
|
-
accessPolicyId TEXT NOT NULL REFERENCES access_policies(id),
|
|
113
|
-
enabled INTEGER DEFAULT 1,
|
|
114
|
-
remark TEXT DEFAULT '',
|
|
115
|
-
createdAt TEXT DEFAULT (datetime('now')),
|
|
116
|
-
updatedAt TEXT DEFAULT (datetime('now'))
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
CREATE INDEX IF NOT EXISTS idx_security_rules_priority ON security_rules(priority);
|
|
120
|
-
CREATE INDEX IF NOT EXISTS idx_security_rules_accessPolicyId ON security_rules(accessPolicyId);
|
|
121
|
-
|
|
122
|
-
CREATE TABLE IF NOT EXISTS access_keys (
|
|
123
|
-
accessKeyId TEXT PRIMARY KEY,
|
|
124
|
-
accessKeyPublic TEXT NOT NULL,
|
|
125
|
-
role TEXT NOT NULL,
|
|
126
|
-
remark TEXT DEFAULT '',
|
|
127
|
-
createdBy TEXT NOT NULL,
|
|
128
|
-
expireAt TEXT,
|
|
129
|
-
lastUsedAt TEXT,
|
|
130
|
-
createdAt TEXT DEFAULT (datetime('now')),
|
|
131
|
-
updatedAt TEXT DEFAULT (datetime('now'))
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
CREATE INDEX IF NOT EXISTS idx_access_keys_createdBy ON access_keys(createdBy);
|
|
135
|
-
CREATE INDEX IF NOT EXISTS idx_access_keys_expireAt ON access_keys(expireAt);
|
|
136
|
-
CREATE INDEX IF NOT EXISTS idx_access_keys_lastUsedAt ON access_keys(lastUsedAt);
|
|
137
|
-
|
|
138
|
-
CREATE TABLE IF NOT EXISTS connect_tokens (
|
|
139
|
-
token TEXT PRIMARY KEY,
|
|
140
|
-
data TEXT NOT NULL,
|
|
141
|
-
expiresAt INTEGER NOT NULL
|
|
142
|
-
);
|
|
143
|
-
`;
|
|
144
|
-
// Module-level flag: schema migration runs once per isolate lifetime.
|
|
145
|
-
// Safe because CREATE TABLE IF NOT EXISTS is idempotent — if the isolate
|
|
146
|
-
// is recycled the flag resets and the migration re-runs harmlessly.
|
|
147
|
-
let schemaMigrated = false;
|
|
148
|
-
let _rulesCache = null;
|
|
149
|
-
let _rulesCacheAt = 0;
|
|
150
|
-
const RULES_CACHE_TTL = 3600_000; // 1h — write-through invalidation handles same-isolate immediately
|
|
151
|
-
// Per-instance rules cache (same TTL + write-through invalidation as global rules cache)
|
|
152
|
-
const _instanceRulesCache = new Map();
|
|
153
|
-
function invalidateRulesCache() {
|
|
154
|
-
_rulesCache = null;
|
|
155
|
-
_rulesCacheAt = 0;
|
|
156
|
-
_instanceRulesCache.clear();
|
|
157
|
-
}
|
|
158
|
-
const _membershipCache = new Map();
|
|
159
|
-
function membershipKey(userDid, instanceDid) {
|
|
160
|
-
return `${userDid}\0${instanceDid}`;
|
|
161
|
-
}
|
|
162
|
-
function invalidateMembership(userDid, instanceDid) {
|
|
163
|
-
_membershipCache.delete(membershipKey(userDid, instanceDid));
|
|
164
|
-
}
|
|
165
|
-
function invalidateMembershipsByInstance(instanceDid) {
|
|
166
|
-
const suffix = `\0${instanceDid}`;
|
|
167
|
-
for (const key of _membershipCache.keys()) {
|
|
168
|
-
if (key.endsWith(suffix))
|
|
169
|
-
_membershipCache.delete(key);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
/** @internal Reset all module-level caches — for testing only. */
|
|
173
|
-
export function _resetForTesting() {
|
|
174
|
-
schemaMigrated = false;
|
|
175
|
-
invalidateRulesCache();
|
|
176
|
-
_membershipCache.clear();
|
|
177
|
-
}
|
|
178
|
-
export class D1Store {
|
|
179
|
-
db;
|
|
180
|
-
constructor(db) {
|
|
181
|
-
this.db = db;
|
|
182
|
-
}
|
|
183
|
-
/** Run schema migration. Idempotent — only executes once per isolate. */
|
|
184
|
-
async migrate() {
|
|
185
|
-
if (schemaMigrated)
|
|
186
|
-
return;
|
|
187
|
-
const statements = SCHEMA_SQL.split(";")
|
|
188
|
-
.map((s) => s.trim())
|
|
189
|
-
.filter((s) => s.length > 0);
|
|
190
|
-
await this.db.batch(statements.map((sql) => this.db.prepare(sql)));
|
|
191
|
-
// Migrations (idempotent — ALTER fails silently if column exists)
|
|
192
|
-
for (const stmt of [
|
|
193
|
-
"ALTER TABLE challenges ADD COLUMN invitationId TEXT",
|
|
194
|
-
"ALTER TABLE users ADD COLUMN sourceDomain TEXT",
|
|
195
|
-
"ALTER TABLE users ADD COLUMN lastLoginDomain TEXT",
|
|
196
|
-
// Step 4: per-instance auth columns
|
|
197
|
-
"ALTER TABLE security_rules ADD COLUMN instance_did TEXT DEFAULT NULL",
|
|
198
|
-
"ALTER TABLE access_keys ADD COLUMN instance_did TEXT DEFAULT NULL",
|
|
199
|
-
"ALTER TABLE audit_logs ADD COLUMN instance_did TEXT DEFAULT NULL",
|
|
200
|
-
"ALTER TABLE access_policies ADD COLUMN instance_did TEXT DEFAULT NULL",
|
|
201
|
-
// Step 5: invitations instance scoping
|
|
202
|
-
"ALTER TABLE invitations ADD COLUMN instance_did TEXT DEFAULT NULL",
|
|
203
|
-
]) {
|
|
204
|
-
try {
|
|
205
|
-
await this.db.prepare(stmt).run();
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
// Column already exists — expected after first migration
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
// Step 4: create memberships table (idempotent)
|
|
212
|
-
await this.db.batch([
|
|
213
|
-
this.db.prepare(`
|
|
214
|
-
CREATE TABLE IF NOT EXISTS memberships (
|
|
215
|
-
user_did TEXT NOT NULL,
|
|
216
|
-
instance_did TEXT NOT NULL,
|
|
217
|
-
role TEXT NOT NULL DEFAULT 'guest',
|
|
218
|
-
invited_by TEXT,
|
|
219
|
-
joined_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
220
|
-
PRIMARY KEY (user_did, instance_did)
|
|
221
|
-
)
|
|
222
|
-
`),
|
|
223
|
-
this.db.prepare(`CREATE INDEX IF NOT EXISTS idx_memberships_instance ON memberships(instance_did)`),
|
|
224
|
-
]);
|
|
225
|
-
// Step 8: create settings table (idempotent)
|
|
226
|
-
await this.db
|
|
227
|
-
.prepare(`CREATE TABLE IF NOT EXISTS settings (
|
|
228
|
-
instance_did TEXT NOT NULL,
|
|
229
|
-
key TEXT NOT NULL,
|
|
230
|
-
value TEXT,
|
|
231
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
232
|
-
PRIMARY KEY (instance_did, key)
|
|
233
|
-
)`)
|
|
234
|
-
.run();
|
|
235
|
-
// Step 9: create verify_codes table for email login (idempotent)
|
|
236
|
-
await this.db
|
|
237
|
-
.prepare(`CREATE TABLE IF NOT EXISTS verify_codes (
|
|
238
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
239
|
-
code TEXT NOT NULL UNIQUE,
|
|
240
|
-
subject TEXT NOT NULL,
|
|
241
|
-
purpose TEXT DEFAULT 'login',
|
|
242
|
-
verified INTEGER DEFAULT 0,
|
|
243
|
-
verified_at TEXT,
|
|
244
|
-
sent INTEGER DEFAULT 0,
|
|
245
|
-
sent_at TEXT,
|
|
246
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
247
|
-
)`)
|
|
248
|
-
.run();
|
|
249
|
-
await this.db
|
|
250
|
-
.prepare("CREATE INDEX IF NOT EXISTS idx_vc_subject ON verify_codes(subject)")
|
|
251
|
-
.run();
|
|
252
|
-
// Step 10: create login_tickets table for federation (idempotent)
|
|
253
|
-
await this.db
|
|
254
|
-
.prepare(`CREATE TABLE IF NOT EXISTS login_tickets (
|
|
255
|
-
ticket TEXT PRIMARY KEY,
|
|
256
|
-
did TEXT NOT NULL,
|
|
257
|
-
target_origin TEXT,
|
|
258
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
259
|
-
expires_at TEXT NOT NULL
|
|
260
|
-
)`)
|
|
261
|
-
.run();
|
|
262
|
-
// Seed built-in access policies + default rule (idempotent via INSERT OR IGNORE)
|
|
263
|
-
await this.ensureBuiltinPolicies();
|
|
264
|
-
await this.ensureDefaultRule();
|
|
265
|
-
schemaMigrated = true;
|
|
266
|
-
}
|
|
267
|
-
/** @internal Backwards-compatible alias for migrate(). */
|
|
268
|
-
async ensureSchema() {
|
|
269
|
-
return this.migrate();
|
|
270
|
-
}
|
|
271
|
-
// ─── Challenge operations ──────────────────────────────────────────────
|
|
272
|
-
async saveChallenge(id, challenge, invitationId) {
|
|
273
|
-
await this.ensureSchema();
|
|
274
|
-
await this.db
|
|
275
|
-
.prepare("INSERT OR REPLACE INTO challenges (id, challenge, invitationId) VALUES (?, ?, ?)")
|
|
276
|
-
.bind(id, challenge, invitationId ?? null)
|
|
277
|
-
.run();
|
|
278
|
-
}
|
|
279
|
-
async getChallenge(id) {
|
|
280
|
-
await this.ensureSchema();
|
|
281
|
-
const row = await this.db
|
|
282
|
-
.prepare("SELECT challenge, invitationId FROM challenges WHERE id = ?")
|
|
283
|
-
.bind(id)
|
|
284
|
-
.first();
|
|
285
|
-
return row ?? null;
|
|
286
|
-
}
|
|
287
|
-
async deleteChallenge(id) {
|
|
288
|
-
await this.ensureSchema();
|
|
289
|
-
await this.db.prepare("DELETE FROM challenges WHERE id = ?").bind(id).run();
|
|
290
|
-
}
|
|
291
|
-
async purgeExpiredChallenges() {
|
|
292
|
-
await this.ensureSchema();
|
|
293
|
-
// Purge challenges older than 5 minutes
|
|
294
|
-
await this.db
|
|
295
|
-
.prepare("DELETE FROM challenges WHERE createdAt < datetime('now', '-5 minutes')")
|
|
296
|
-
.run();
|
|
297
|
-
}
|
|
298
|
-
async isRegistrationOpen() {
|
|
299
|
-
await this.ensureSchema();
|
|
300
|
-
const row = await this.db
|
|
301
|
-
.prepare("SELECT accessPolicyId FROM security_rules WHERE id = 'default'")
|
|
302
|
-
.first();
|
|
303
|
-
return row?.accessPolicyId === "public";
|
|
304
|
-
}
|
|
305
|
-
// ─── User operations ──────────────────────────────────────────────────
|
|
306
|
-
async createUser(params) {
|
|
307
|
-
await this.ensureSchema();
|
|
308
|
-
const now = new Date().toISOString();
|
|
309
|
-
await this.db
|
|
310
|
-
.prepare(`INSERT INTO users (did, pk, fullName, email, sourceProvider, firstLoginAt, lastLoginAt, lastLoginIp, sourceDomain, lastLoginDomain, createdAt, updatedAt)
|
|
311
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
312
|
-
.bind(params.did, params.pk, params.fullName ?? null, params.email ?? null, params.sourceProvider, now, now, params.ip ?? null, params.domain ?? null, params.domain ?? null, now, now)
|
|
313
|
-
.run();
|
|
314
|
-
}
|
|
315
|
-
async getUserByDid(did) {
|
|
316
|
-
await this.ensureSchema();
|
|
317
|
-
return this.db.prepare("SELECT * FROM users WHERE did = ?").bind(did).first();
|
|
318
|
-
}
|
|
319
|
-
async updateLastLogin(did, ip, domain) {
|
|
320
|
-
await this.ensureSchema();
|
|
321
|
-
const now = new Date().toISOString();
|
|
322
|
-
await this.db
|
|
323
|
-
.prepare("UPDATE users SET lastLoginAt = ?, lastLoginIp = ?, lastLoginDomain = ?, updatedAt = ? WHERE did = ?")
|
|
324
|
-
.bind(now, ip ?? null, domain ?? null, now, did)
|
|
325
|
-
.run();
|
|
326
|
-
}
|
|
327
|
-
async incrementPasskeyCount(did) {
|
|
328
|
-
await this.ensureSchema();
|
|
329
|
-
await this.db
|
|
330
|
-
.prepare("UPDATE users SET passkeyCount = passkeyCount + 1, updatedAt = ? WHERE did = ?")
|
|
331
|
-
.bind(new Date().toISOString(), did)
|
|
332
|
-
.run();
|
|
333
|
-
}
|
|
334
|
-
// ─── Team: User queries ────────────────────────────────────────────────
|
|
335
|
-
async getUserCount() {
|
|
336
|
-
await this.ensureSchema();
|
|
337
|
-
const row = await this.db
|
|
338
|
-
.prepare("SELECT COUNT(*) as count FROM users")
|
|
339
|
-
.first();
|
|
340
|
-
return Number(row?.count ?? 0);
|
|
341
|
-
}
|
|
342
|
-
async getUsers(opts) {
|
|
343
|
-
await this.ensureSchema();
|
|
344
|
-
const conditions = [];
|
|
345
|
-
const params = [];
|
|
346
|
-
if (opts.role) {
|
|
347
|
-
conditions.push("u.role = ?");
|
|
348
|
-
params.push(opts.role);
|
|
349
|
-
}
|
|
350
|
-
if (opts.approved !== undefined) {
|
|
351
|
-
conditions.push("u.approved = ?");
|
|
352
|
-
params.push(opts.approved);
|
|
353
|
-
}
|
|
354
|
-
if (opts.search) {
|
|
355
|
-
conditions.push("(u.fullName LIKE ? OR u.email LIKE ? OR u.did LIKE ?)");
|
|
356
|
-
const like = `%${opts.search}%`;
|
|
357
|
-
params.push(like, like, like);
|
|
358
|
-
}
|
|
359
|
-
if (opts.sourceProvider) {
|
|
360
|
-
conditions.push("u.sourceProvider = ?");
|
|
361
|
-
params.push(opts.sourceProvider);
|
|
362
|
-
}
|
|
363
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
364
|
-
const offset = (opts.page - 1) * opts.pageSize;
|
|
365
|
-
// Count query
|
|
366
|
-
const countRow = await this.db
|
|
367
|
-
.prepare(`SELECT COUNT(*) as count FROM users u ${where}`)
|
|
368
|
-
.bind(...params)
|
|
369
|
-
.first();
|
|
370
|
-
const total = countRow?.count ?? 0;
|
|
371
|
-
// Data query with JOIN to resolve inviter name
|
|
372
|
-
const rows = await this.db
|
|
373
|
-
.prepare(`SELECT u.did, u.fullName, u.email, u.avatar, u.role, u.approved, u.createdAt, u.lastLoginAt, u.passkeyCount, u.sourceDomain, u.lastLoginDomain, u.sourceProvider, u.inviter,
|
|
374
|
-
inv.fullName as inviterName
|
|
375
|
-
FROM users u
|
|
376
|
-
LEFT JOIN users inv ON u.inviter = inv.did
|
|
377
|
-
${where}
|
|
378
|
-
ORDER BY u.createdAt DESC
|
|
379
|
-
LIMIT ? OFFSET ?`)
|
|
380
|
-
.bind(...params, opts.pageSize, offset)
|
|
381
|
-
.all();
|
|
382
|
-
return { users: rows.results ?? [], total };
|
|
383
|
-
}
|
|
384
|
-
async getMemberInfo(did) {
|
|
385
|
-
await this.ensureSchema();
|
|
386
|
-
const row = await this.db
|
|
387
|
-
.prepare(`SELECT u.did, u.fullName, u.email, u.avatar, u.role, u.approved, u.createdAt, u.lastLoginAt, u.passkeyCount, u.sourceDomain, u.lastLoginDomain, u.sourceProvider, u.inviter,
|
|
388
|
-
inv.fullName as inviterName
|
|
389
|
-
FROM users u
|
|
390
|
-
LEFT JOIN users inv ON u.inviter = inv.did
|
|
391
|
-
WHERE u.did = ?`)
|
|
392
|
-
.bind(did)
|
|
393
|
-
.first();
|
|
394
|
-
return row ?? null;
|
|
395
|
-
}
|
|
396
|
-
async updateUserRole(did, role) {
|
|
397
|
-
await this.ensureSchema();
|
|
398
|
-
await this.db
|
|
399
|
-
.prepare("UPDATE users SET role = ?, updatedAt = ? WHERE did = ?")
|
|
400
|
-
.bind(role, new Date().toISOString(), did)
|
|
401
|
-
.run();
|
|
402
|
-
}
|
|
403
|
-
async updateUserApproval(did, approved) {
|
|
404
|
-
await this.ensureSchema();
|
|
405
|
-
await this.db
|
|
406
|
-
.prepare("UPDATE users SET approved = ?, updatedAt = ? WHERE did = ?")
|
|
407
|
-
.bind(approved ? 1 : 0, new Date().toISOString(), did)
|
|
408
|
-
.run();
|
|
409
|
-
}
|
|
410
|
-
async updateUserProfile(did, fields) {
|
|
411
|
-
await this.ensureSchema();
|
|
412
|
-
const sets = [];
|
|
413
|
-
const params = [];
|
|
414
|
-
if (fields.fullName !== undefined) {
|
|
415
|
-
sets.push("fullName = ?");
|
|
416
|
-
params.push(fields.fullName);
|
|
417
|
-
}
|
|
418
|
-
if (fields.email !== undefined) {
|
|
419
|
-
sets.push("email = ?");
|
|
420
|
-
params.push(fields.email);
|
|
421
|
-
}
|
|
422
|
-
if (fields.avatar !== undefined) {
|
|
423
|
-
sets.push("avatar = ?");
|
|
424
|
-
params.push(fields.avatar);
|
|
425
|
-
}
|
|
426
|
-
if (sets.length === 0)
|
|
427
|
-
return;
|
|
428
|
-
sets.push("updatedAt = ?");
|
|
429
|
-
params.push(new Date().toISOString());
|
|
430
|
-
params.push(did);
|
|
431
|
-
await this.db
|
|
432
|
-
.prepare(`UPDATE users SET ${sets.join(", ")} WHERE did = ?`)
|
|
433
|
-
.bind(...params)
|
|
434
|
-
.run();
|
|
435
|
-
}
|
|
436
|
-
async removeUser(did) {
|
|
437
|
-
await this.ensureSchema();
|
|
438
|
-
// Delete connected accounts first, then user
|
|
439
|
-
await this.db.batch([
|
|
440
|
-
this.db.prepare("DELETE FROM connected_accounts WHERE userDid = ?").bind(did),
|
|
441
|
-
this.db.prepare("DELETE FROM users WHERE did = ?").bind(did),
|
|
442
|
-
]);
|
|
443
|
-
}
|
|
444
|
-
async setUserInviter(did, inviterDid) {
|
|
445
|
-
await this.ensureSchema();
|
|
446
|
-
await this.db
|
|
447
|
-
.prepare("UPDATE users SET inviter = ?, updatedAt = ? WHERE did = ? AND inviter IS NULL")
|
|
448
|
-
.bind(inviterDid, new Date().toISOString(), did)
|
|
449
|
-
.run();
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Atomically transfer ownership: set target to owner, caller to admin.
|
|
453
|
-
* Uses D1 batch for atomicity.
|
|
454
|
-
*/
|
|
455
|
-
async transferOwnership(currentOwnerDid, newOwnerDid) {
|
|
456
|
-
await this.ensureSchema();
|
|
457
|
-
const now = new Date().toISOString();
|
|
458
|
-
await this.db.batch([
|
|
459
|
-
this.db
|
|
460
|
-
.prepare("UPDATE users SET role = 'owner', updatedAt = ? WHERE did = ?")
|
|
461
|
-
.bind(now, newOwnerDid),
|
|
462
|
-
this.db
|
|
463
|
-
.prepare("UPDATE users SET role = 'admin', updatedAt = ? WHERE did = ?")
|
|
464
|
-
.bind(now, currentOwnerDid),
|
|
465
|
-
]);
|
|
466
|
-
}
|
|
467
|
-
// ─── Team: Invitations ────────────────────────────────────────────────
|
|
468
|
-
async createInvitation(input) {
|
|
469
|
-
await this.ensureSchema();
|
|
470
|
-
const id = crypto.randomUUID();
|
|
471
|
-
const now = new Date().toISOString();
|
|
472
|
-
const expireHours = input.expireHours ?? 168; // 7 days
|
|
473
|
-
const maxUses = input.maxUses ?? 1;
|
|
474
|
-
const expireAt = new Date(Date.now() + expireHours * 60 * 60 * 1000).toISOString();
|
|
475
|
-
await this.db
|
|
476
|
-
.prepare(`INSERT INTO invitations (id, teamDid, role, remark, inviterDid, expireAt, maxUses, useCount, status, instance_did, createdAt, updatedAt)
|
|
477
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, 'active', ?, ?, ?)`)
|
|
478
|
-
.bind(id, input.teamDid, input.role, input.remark ?? "", input.inviterDid, expireAt, maxUses, input.instanceDid ?? null, now, now)
|
|
479
|
-
.run();
|
|
480
|
-
return {
|
|
481
|
-
id,
|
|
482
|
-
teamDid: input.teamDid,
|
|
483
|
-
role: input.role,
|
|
484
|
-
remark: input.remark ?? "",
|
|
485
|
-
inviterDid: input.inviterDid,
|
|
486
|
-
expireAt,
|
|
487
|
-
maxUses,
|
|
488
|
-
useCount: 0,
|
|
489
|
-
status: "active",
|
|
490
|
-
createdAt: now,
|
|
491
|
-
updatedAt: now,
|
|
492
|
-
instance_did: input.instanceDid ?? null,
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
async getInvitation(id) {
|
|
496
|
-
await this.ensureSchema();
|
|
497
|
-
return this.db
|
|
498
|
-
.prepare("SELECT * FROM invitations WHERE id = ?")
|
|
499
|
-
.bind(id)
|
|
500
|
-
.first();
|
|
501
|
-
}
|
|
502
|
-
async getInvitations(opts) {
|
|
503
|
-
await this.ensureSchema();
|
|
504
|
-
const offset = (opts.page - 1) * opts.pageSize;
|
|
505
|
-
const conditions = [];
|
|
506
|
-
const params = [];
|
|
507
|
-
if (opts.instanceDid !== undefined) {
|
|
508
|
-
conditions.push("i.instance_did = ?");
|
|
509
|
-
params.push(opts.instanceDid);
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
conditions.push("i.instance_did IS NULL");
|
|
513
|
-
}
|
|
514
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
515
|
-
const countRow = await this.db
|
|
516
|
-
.prepare(`SELECT COUNT(*) as count FROM invitations i ${where}`)
|
|
517
|
-
.bind(...params)
|
|
518
|
-
.first();
|
|
519
|
-
const total = countRow?.count ?? 0;
|
|
520
|
-
const rows = await this.db
|
|
521
|
-
.prepare(`SELECT i.*, u.fullName as inviterName
|
|
522
|
-
FROM invitations i
|
|
523
|
-
LEFT JOIN users u ON i.inviterDid = u.did
|
|
524
|
-
${where}
|
|
525
|
-
ORDER BY i.createdAt DESC
|
|
526
|
-
LIMIT ? OFFSET ?`)
|
|
527
|
-
.bind(...params, opts.pageSize, offset)
|
|
528
|
-
.all();
|
|
529
|
-
return { invitations: rows.results ?? [], total };
|
|
530
|
-
}
|
|
531
|
-
/**
|
|
532
|
-
* Atomically increment useCount. Returns false if maxUses already reached.
|
|
533
|
-
*/
|
|
534
|
-
async incrementInvitationUseCount(id) {
|
|
535
|
-
await this.ensureSchema();
|
|
536
|
-
const result = await this.db
|
|
537
|
-
.prepare(`UPDATE invitations SET useCount = useCount + 1, updatedAt = ?
|
|
538
|
-
WHERE id = ? AND useCount < maxUses AND status = 'active'`)
|
|
539
|
-
.bind(new Date().toISOString(), id)
|
|
540
|
-
.run();
|
|
541
|
-
return (result.meta?.changes ?? 0) > 0;
|
|
542
|
-
}
|
|
543
|
-
async updateInvitationStatus(id, status) {
|
|
544
|
-
await this.ensureSchema();
|
|
545
|
-
await this.db
|
|
546
|
-
.prepare("UPDATE invitations SET status = ?, updatedAt = ? WHERE id = ?")
|
|
547
|
-
.bind(status, new Date().toISOString(), id)
|
|
548
|
-
.run();
|
|
549
|
-
}
|
|
550
|
-
async deleteInvitation(id) {
|
|
551
|
-
await this.ensureSchema();
|
|
552
|
-
await this.db.prepare("DELETE FROM invitations WHERE id = ?").bind(id).run();
|
|
553
|
-
}
|
|
554
|
-
async purgeExpiredInvitations() {
|
|
555
|
-
await this.ensureSchema();
|
|
556
|
-
await this.db
|
|
557
|
-
.prepare("UPDATE invitations SET status = 'expired', updatedAt = ? WHERE status = 'active' AND expireAt < datetime('now')")
|
|
558
|
-
.bind(new Date().toISOString())
|
|
559
|
-
.run();
|
|
560
|
-
}
|
|
561
|
-
// ─── Team: Audit logs ─────────────────────────────────────────────────
|
|
562
|
-
async createAuditLog(input) {
|
|
563
|
-
await this.ensureSchema();
|
|
564
|
-
await this.db
|
|
565
|
-
.prepare(`INSERT INTO audit_logs (action, operatorDid, targetDid, metadata, ip, instance_did, createdAt)
|
|
566
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
567
|
-
.bind(input.action, input.operatorDid, input.targetDid ?? null, JSON.stringify(input.metadata ?? {}), input.ip ?? null, input.instanceDid ?? null, new Date().toISOString())
|
|
568
|
-
.run();
|
|
569
|
-
}
|
|
570
|
-
async getAuditLogs(opts) {
|
|
571
|
-
await this.ensureSchema();
|
|
572
|
-
const conditions = [];
|
|
573
|
-
const params = [];
|
|
574
|
-
if (opts.action) {
|
|
575
|
-
conditions.push("a.action = ?");
|
|
576
|
-
params.push(opts.action);
|
|
577
|
-
}
|
|
578
|
-
if (opts.instanceDid !== undefined) {
|
|
579
|
-
conditions.push("a.instance_did = ?");
|
|
580
|
-
params.push(opts.instanceDid);
|
|
581
|
-
}
|
|
582
|
-
else {
|
|
583
|
-
conditions.push("a.instance_did IS NULL");
|
|
584
|
-
}
|
|
585
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
586
|
-
const offset = (opts.page - 1) * opts.pageSize;
|
|
587
|
-
const countRow = await this.db
|
|
588
|
-
.prepare(`SELECT COUNT(*) as count FROM audit_logs a ${where}`)
|
|
589
|
-
.bind(...params)
|
|
590
|
-
.first();
|
|
591
|
-
const total = countRow?.count ?? 0;
|
|
592
|
-
const rows = await this.db
|
|
593
|
-
.prepare(`SELECT a.*, op.fullName as operatorName, tgt.fullName as targetName
|
|
594
|
-
FROM audit_logs a
|
|
595
|
-
LEFT JOIN users op ON a.operatorDid = op.did
|
|
596
|
-
LEFT JOIN users tgt ON a.targetDid = tgt.did
|
|
597
|
-
${where}
|
|
598
|
-
ORDER BY a.createdAt DESC
|
|
599
|
-
LIMIT ? OFFSET ?`)
|
|
600
|
-
.bind(...params, opts.pageSize, offset)
|
|
601
|
-
.all();
|
|
602
|
-
return { logs: rows.results ?? [], total };
|
|
603
|
-
}
|
|
604
|
-
// ─── Instance-scoped audit log operations ────────────────────────────
|
|
605
|
-
async getAuditLogsForInstance(instanceDid, opts) {
|
|
606
|
-
await this.ensureSchema();
|
|
607
|
-
const conditions = ["a.instance_did = ?"];
|
|
608
|
-
const params = [instanceDid];
|
|
609
|
-
if (opts.action) {
|
|
610
|
-
conditions.push("a.action = ?");
|
|
611
|
-
params.push(opts.action);
|
|
612
|
-
}
|
|
613
|
-
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
614
|
-
const offset = (opts.page - 1) * opts.pageSize;
|
|
615
|
-
const countRow = await this.db
|
|
616
|
-
.prepare(`SELECT COUNT(*) as count FROM audit_logs a ${where}`)
|
|
617
|
-
.bind(...params)
|
|
618
|
-
.first();
|
|
619
|
-
const total = countRow?.count ?? 0;
|
|
620
|
-
const rows = await this.db
|
|
621
|
-
.prepare(`SELECT a.*, op.fullName as operatorName, tgt.fullName as targetName
|
|
622
|
-
FROM audit_logs a
|
|
623
|
-
LEFT JOIN users op ON a.operatorDid = op.did
|
|
624
|
-
LEFT JOIN users tgt ON a.targetDid = tgt.did
|
|
625
|
-
${where}
|
|
626
|
-
ORDER BY a.createdAt DESC
|
|
627
|
-
LIMIT ? OFFSET ?`)
|
|
628
|
-
.bind(...params, opts.pageSize, offset)
|
|
629
|
-
.all();
|
|
630
|
-
return { logs: rows.results ?? [], total };
|
|
631
|
-
}
|
|
632
|
-
async getAuditLogById(id, instanceDid) {
|
|
633
|
-
await this.ensureSchema();
|
|
634
|
-
return this.db
|
|
635
|
-
.prepare(`SELECT a.*, op.fullName as operatorName, tgt.fullName as targetName
|
|
636
|
-
FROM audit_logs a
|
|
637
|
-
LEFT JOIN users op ON a.operatorDid = op.did
|
|
638
|
-
LEFT JOIN users tgt ON a.targetDid = tgt.did
|
|
639
|
-
WHERE a.id = ? AND a.instance_did = ?`)
|
|
640
|
-
.bind(id, instanceDid)
|
|
641
|
-
.first();
|
|
642
|
-
}
|
|
643
|
-
// ─── Connected accounts operations ────────────────────────────────────
|
|
644
|
-
async createConnectedAccount(params) {
|
|
645
|
-
await this.ensureSchema();
|
|
646
|
-
const now = new Date().toISOString();
|
|
647
|
-
await this.db
|
|
648
|
-
.prepare(`INSERT INTO connected_accounts (did, pk, userDid, provider, id, extra, userInfo, firstLoginAt, lastLoginAt, lastLoginIp)
|
|
649
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
650
|
-
.bind(params.did, params.pk, params.userDid, params.provider, params.id, params.extra, params.userInfo, now, now, params.ip ?? null)
|
|
651
|
-
.run();
|
|
652
|
-
}
|
|
653
|
-
async upsertConnectedAccount(account) {
|
|
654
|
-
await this.ensureSchema();
|
|
655
|
-
await this.db
|
|
656
|
-
.prepare(`INSERT INTO connected_accounts (did, pk, userDid, provider, id, userInfo, firstLoginAt, lastLoginAt)
|
|
657
|
-
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
|
658
|
-
ON CONFLICT (did) DO UPDATE SET
|
|
659
|
-
pk = excluded.pk, lastLoginAt = datetime('now'),
|
|
660
|
-
userInfo = COALESCE(excluded.userInfo, connected_accounts.userInfo)`)
|
|
661
|
-
.bind(account.did, account.pk, account.userDid, account.provider, account.id, account.userInfo || null)
|
|
662
|
-
.run();
|
|
663
|
-
}
|
|
664
|
-
async getConnectedAccountById(credentialId) {
|
|
665
|
-
await this.ensureSchema();
|
|
666
|
-
return this.db
|
|
667
|
-
.prepare("SELECT * FROM connected_accounts WHERE id = ?")
|
|
668
|
-
.bind(credentialId)
|
|
669
|
-
.first();
|
|
670
|
-
}
|
|
671
|
-
async getConnectedAccountByDid(did) {
|
|
672
|
-
await this.ensureSchema();
|
|
673
|
-
return this.db
|
|
674
|
-
.prepare("SELECT * FROM connected_accounts WHERE did = ?")
|
|
675
|
-
.bind(did)
|
|
676
|
-
.first();
|
|
677
|
-
}
|
|
678
|
-
async updateCounter(did, counter) {
|
|
679
|
-
await this.ensureSchema();
|
|
680
|
-
await this.db
|
|
681
|
-
.prepare("UPDATE connected_accounts SET counter = ?, lastLoginAt = ? WHERE did = ?")
|
|
682
|
-
.bind(counter, new Date().toISOString(), did)
|
|
683
|
-
.run();
|
|
684
|
-
}
|
|
685
|
-
async getConnectedAccountsByUserDid(userDid) {
|
|
686
|
-
await this.ensureSchema();
|
|
687
|
-
const rows = await this.db
|
|
688
|
-
.prepare("SELECT * FROM connected_accounts WHERE userDid = ?")
|
|
689
|
-
.bind(userDid)
|
|
690
|
-
.all();
|
|
691
|
-
return rows.results ?? [];
|
|
692
|
-
}
|
|
693
|
-
async getConnectedAccountByProviderAndUser(provider, userDid) {
|
|
694
|
-
await this.ensureSchema();
|
|
695
|
-
return this.db
|
|
696
|
-
.prepare("SELECT * FROM connected_accounts WHERE provider = ? AND userDid = ?")
|
|
697
|
-
.bind(provider, userDid)
|
|
698
|
-
.first();
|
|
699
|
-
}
|
|
700
|
-
async deleteConnectedAccount(did) {
|
|
701
|
-
await this.ensureSchema();
|
|
702
|
-
await this.db.prepare("DELETE FROM connected_accounts WHERE did = ?").bind(did).run();
|
|
703
|
-
}
|
|
704
|
-
// ─── Access keys ────────────────────────────────────────────────────────
|
|
705
|
-
async createAccessKey(input) {
|
|
706
|
-
await this.ensureSchema();
|
|
707
|
-
const now = new Date().toISOString();
|
|
708
|
-
await this.db
|
|
709
|
-
.prepare(`INSERT INTO access_keys (accessKeyId, accessKeyPublic, role, remark, createdBy, expireAt, instance_did, createdAt, updatedAt)
|
|
710
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
711
|
-
.bind(input.accessKeyId, input.accessKeyPublic, input.role, input.remark ?? "", input.createdBy, input.expireAt ?? null, input.instanceDid ?? null, now, now)
|
|
712
|
-
.run();
|
|
713
|
-
return {
|
|
714
|
-
accessKeyId: input.accessKeyId,
|
|
715
|
-
accessKeyPublic: input.accessKeyPublic,
|
|
716
|
-
role: input.role,
|
|
717
|
-
remark: input.remark ?? "",
|
|
718
|
-
createdBy: input.createdBy,
|
|
719
|
-
expireAt: input.expireAt ?? null,
|
|
720
|
-
lastUsedAt: null,
|
|
721
|
-
createdAt: now,
|
|
722
|
-
updatedAt: now,
|
|
723
|
-
instance_did: input.instanceDid ?? null,
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
async getAccessKeyById(accessKeyId) {
|
|
727
|
-
await this.ensureSchema();
|
|
728
|
-
const row = await this.db
|
|
729
|
-
.prepare(`SELECT k.*, u.fullName as createdByName
|
|
730
|
-
FROM access_keys k
|
|
731
|
-
LEFT JOIN users u ON k.createdBy = u.did
|
|
732
|
-
WHERE k.accessKeyId = ?`)
|
|
733
|
-
.bind(accessKeyId)
|
|
734
|
-
.first();
|
|
735
|
-
if (!row)
|
|
736
|
-
return null;
|
|
737
|
-
const { instance_did, ...rest } = row;
|
|
738
|
-
return { ...rest, createdByName: row.createdByName ?? null, instanceDid: instance_did ?? null };
|
|
739
|
-
}
|
|
740
|
-
async getAccessKeys(opts) {
|
|
741
|
-
await this.ensureSchema();
|
|
742
|
-
const conditions = [];
|
|
743
|
-
const params = [];
|
|
744
|
-
if (opts.createdBy) {
|
|
745
|
-
conditions.push("k.createdBy = ?");
|
|
746
|
-
params.push(opts.createdBy);
|
|
747
|
-
}
|
|
748
|
-
if (opts.search) {
|
|
749
|
-
conditions.push("(k.remark LIKE ? OR k.accessKeyId LIKE ?)");
|
|
750
|
-
const like = `%${opts.search}%`;
|
|
751
|
-
params.push(like, like);
|
|
752
|
-
}
|
|
753
|
-
if (opts.instanceDid !== undefined) {
|
|
754
|
-
conditions.push("k.instance_did = ?");
|
|
755
|
-
params.push(opts.instanceDid);
|
|
756
|
-
}
|
|
757
|
-
else {
|
|
758
|
-
conditions.push("k.instance_did IS NULL");
|
|
759
|
-
}
|
|
760
|
-
const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
761
|
-
const offset = (opts.page - 1) * opts.pageSize;
|
|
762
|
-
const countRow = await this.db
|
|
763
|
-
.prepare(`SELECT COUNT(*) as count FROM access_keys k ${where}`)
|
|
764
|
-
.bind(...params)
|
|
765
|
-
.first();
|
|
766
|
-
const total = countRow?.count ?? 0;
|
|
767
|
-
const rows = await this.db
|
|
768
|
-
.prepare(`SELECT k.*, u.fullName as createdByName
|
|
769
|
-
FROM access_keys k
|
|
770
|
-
LEFT JOIN users u ON k.createdBy = u.did
|
|
771
|
-
${where}
|
|
772
|
-
ORDER BY k.createdAt DESC
|
|
773
|
-
LIMIT ? OFFSET ?`)
|
|
774
|
-
.bind(...params, opts.pageSize, offset)
|
|
775
|
-
.all();
|
|
776
|
-
return {
|
|
777
|
-
keys: (rows.results ?? []).map((r) => {
|
|
778
|
-
const { instance_did, ...rest } = r;
|
|
779
|
-
return {
|
|
780
|
-
...rest,
|
|
781
|
-
createdByName: r.createdByName ?? null,
|
|
782
|
-
instanceDid: instance_did ?? null,
|
|
783
|
-
};
|
|
784
|
-
}),
|
|
785
|
-
total,
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
async updateAccessKey(accessKeyId, input) {
|
|
789
|
-
await this.ensureSchema();
|
|
790
|
-
const sets = [];
|
|
791
|
-
const params = [];
|
|
792
|
-
if (input.remark !== undefined) {
|
|
793
|
-
sets.push("remark = ?");
|
|
794
|
-
params.push(input.remark);
|
|
795
|
-
}
|
|
796
|
-
if ("expireAt" in input) {
|
|
797
|
-
sets.push("expireAt = ?");
|
|
798
|
-
params.push(input.expireAt ?? null);
|
|
799
|
-
}
|
|
800
|
-
if (sets.length > 0) {
|
|
801
|
-
sets.push("updatedAt = ?");
|
|
802
|
-
params.push(new Date().toISOString());
|
|
803
|
-
params.push(accessKeyId);
|
|
804
|
-
await this.db
|
|
805
|
-
.prepare(`UPDATE access_keys SET ${sets.join(", ")} WHERE accessKeyId = ?`)
|
|
806
|
-
.bind(...params)
|
|
807
|
-
.run();
|
|
808
|
-
}
|
|
809
|
-
return this.getAccessKeyById(accessKeyId);
|
|
810
|
-
}
|
|
811
|
-
async deleteAccessKey(accessKeyId) {
|
|
812
|
-
await this.ensureSchema();
|
|
813
|
-
await this.db.prepare("DELETE FROM access_keys WHERE accessKeyId = ?").bind(accessKeyId).run();
|
|
814
|
-
}
|
|
815
|
-
async refreshAccessKeyLastUsed(accessKeyId) {
|
|
816
|
-
await this.ensureSchema();
|
|
817
|
-
await this.db
|
|
818
|
-
.prepare("UPDATE access_keys SET lastUsedAt = ? WHERE accessKeyId = ?")
|
|
819
|
-
.bind(new Date().toISOString(), accessKeyId)
|
|
820
|
-
.run();
|
|
821
|
-
}
|
|
822
|
-
// ─── Access policies: seeding ──────────────────────────────────────────
|
|
823
|
-
async ensureBuiltinPolicies() {
|
|
824
|
-
const policies = [
|
|
825
|
-
{
|
|
826
|
-
id: "public",
|
|
827
|
-
name: "Public",
|
|
828
|
-
description: "Anyone can access, no login required",
|
|
829
|
-
roles: null,
|
|
830
|
-
reverse: 0,
|
|
831
|
-
},
|
|
832
|
-
{
|
|
833
|
-
id: "invited-only",
|
|
834
|
-
name: "Invited Only",
|
|
835
|
-
description: "Any logged-in user can access",
|
|
836
|
-
roles: "[]",
|
|
837
|
-
reverse: 1,
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
id: "admin-only",
|
|
841
|
-
name: "Admin Only",
|
|
842
|
-
description: "Only owner and admin can access",
|
|
843
|
-
roles: '["owner","admin"]',
|
|
844
|
-
reverse: 0,
|
|
845
|
-
},
|
|
846
|
-
{
|
|
847
|
-
id: "owner-only",
|
|
848
|
-
name: "Owner Only",
|
|
849
|
-
description: "Only the owner can access",
|
|
850
|
-
roles: '["owner"]',
|
|
851
|
-
reverse: 0,
|
|
852
|
-
},
|
|
853
|
-
];
|
|
854
|
-
const now = new Date().toISOString();
|
|
855
|
-
await this.db.batch(policies.map((p) => this.db
|
|
856
|
-
.prepare(`INSERT OR IGNORE INTO access_policies (id, name, description, roles, reverse, isProtected, createdAt, updatedAt)
|
|
857
|
-
VALUES (?, ?, ?, ?, ?, 1, ?, ?)`)
|
|
858
|
-
.bind(p.id, p.name, p.description, p.roles, p.reverse, now, now)));
|
|
859
|
-
invalidateRulesCache();
|
|
860
|
-
}
|
|
861
|
-
async ensureDefaultRule() {
|
|
862
|
-
const now = new Date().toISOString();
|
|
863
|
-
await this.db
|
|
864
|
-
.prepare(`INSERT OR IGNORE INTO security_rules (id, pathPattern, priority, accessPolicyId, enabled, remark, createdAt, updatedAt)
|
|
865
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
866
|
-
.bind("default", "*", -1, "public", 1, "Default fallback rule", now, now)
|
|
867
|
-
.run();
|
|
868
|
-
invalidateRulesCache();
|
|
869
|
-
}
|
|
870
|
-
// ─── Access policies: CRUD ─────────────────────────────────────────────
|
|
871
|
-
async getAccessPolicies(instanceDid) {
|
|
872
|
-
await this.ensureSchema();
|
|
873
|
-
let sql = "SELECT * FROM access_policies";
|
|
874
|
-
const params = [];
|
|
875
|
-
if (instanceDid !== undefined) {
|
|
876
|
-
sql += " WHERE instance_did = ?";
|
|
877
|
-
params.push(instanceDid);
|
|
878
|
-
}
|
|
879
|
-
else {
|
|
880
|
-
sql += " WHERE instance_did IS NULL";
|
|
881
|
-
}
|
|
882
|
-
sql += " ORDER BY isProtected DESC, createdAt ASC";
|
|
883
|
-
const rows = await this.db
|
|
884
|
-
.prepare(sql)
|
|
885
|
-
.bind(...params)
|
|
886
|
-
.all();
|
|
887
|
-
const policies = rows.results ?? [];
|
|
888
|
-
const result = [];
|
|
889
|
-
for (const p of policies) {
|
|
890
|
-
const ruleCount = await this.getAccessPolicyRuleCount(p.id);
|
|
891
|
-
const { accessType, roles } = dbToAccessType(p.roles, p.reverse);
|
|
892
|
-
result.push({
|
|
893
|
-
id: p.id,
|
|
894
|
-
name: p.name,
|
|
895
|
-
description: p.description,
|
|
896
|
-
roles: roles ?? (p.roles !== null ? JSON.parse(p.roles) : null),
|
|
897
|
-
reverse: p.reverse === 1,
|
|
898
|
-
isProtected: p.isProtected === 1,
|
|
899
|
-
accessType,
|
|
900
|
-
ruleCount,
|
|
901
|
-
createdAt: p.createdAt,
|
|
902
|
-
updatedAt: p.updatedAt,
|
|
903
|
-
instanceDid: p.instance_did ?? null,
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
return result;
|
|
907
|
-
}
|
|
908
|
-
async getAccessPolicy(id) {
|
|
909
|
-
await this.ensureSchema();
|
|
910
|
-
const p = await this.db
|
|
911
|
-
.prepare("SELECT * FROM access_policies WHERE id = ?")
|
|
912
|
-
.bind(id)
|
|
913
|
-
.first();
|
|
914
|
-
if (!p)
|
|
915
|
-
return null;
|
|
916
|
-
const ruleCount = await this.getAccessPolicyRuleCount(p.id);
|
|
917
|
-
const { accessType, roles } = dbToAccessType(p.roles, p.reverse);
|
|
918
|
-
return {
|
|
919
|
-
id: p.id,
|
|
920
|
-
name: p.name,
|
|
921
|
-
description: p.description,
|
|
922
|
-
roles: roles ?? (p.roles !== null ? JSON.parse(p.roles) : null),
|
|
923
|
-
reverse: p.reverse === 1,
|
|
924
|
-
isProtected: p.isProtected === 1,
|
|
925
|
-
accessType,
|
|
926
|
-
ruleCount,
|
|
927
|
-
createdAt: p.createdAt,
|
|
928
|
-
updatedAt: p.updatedAt,
|
|
929
|
-
instanceDid: p.instance_did ?? null,
|
|
930
|
-
};
|
|
931
|
-
}
|
|
932
|
-
async createAccessPolicy(input) {
|
|
933
|
-
await this.ensureSchema();
|
|
934
|
-
const id = crypto.randomUUID();
|
|
935
|
-
const now = new Date().toISOString();
|
|
936
|
-
const { roles: dbRoles, reverse } = await import("./access-policy.js").then((m) => m.accessTypeToDb(input.accessType, input.roles));
|
|
937
|
-
await this.db
|
|
938
|
-
.prepare(`INSERT INTO access_policies (id, name, description, roles, reverse, isProtected, instance_did, createdAt, updatedAt)
|
|
939
|
-
VALUES (?, ?, ?, ?, ?, 0, ?, ?, ?)`)
|
|
940
|
-
.bind(id, input.name, input.description ?? "", dbRoles, reverse, input.instanceDid ?? null, now, now)
|
|
941
|
-
.run();
|
|
942
|
-
invalidateRulesCache();
|
|
943
|
-
return (await this.getAccessPolicy(id));
|
|
944
|
-
}
|
|
945
|
-
async updateAccessPolicy(id, input) {
|
|
946
|
-
await this.ensureSchema();
|
|
947
|
-
const now = new Date().toISOString();
|
|
948
|
-
const sets = [];
|
|
949
|
-
const params = [];
|
|
950
|
-
if (input.name !== undefined) {
|
|
951
|
-
sets.push("name = ?");
|
|
952
|
-
params.push(input.name);
|
|
953
|
-
}
|
|
954
|
-
if (input.description !== undefined) {
|
|
955
|
-
sets.push("description = ?");
|
|
956
|
-
params.push(input.description);
|
|
957
|
-
}
|
|
958
|
-
if (input.accessType !== undefined) {
|
|
959
|
-
const { roles: dbRoles, reverse } = await import("./access-policy.js").then((m) => m.accessTypeToDb(input.accessType, input.roles));
|
|
960
|
-
sets.push("roles = ?");
|
|
961
|
-
params.push(dbRoles);
|
|
962
|
-
sets.push("reverse = ?");
|
|
963
|
-
params.push(reverse);
|
|
964
|
-
}
|
|
965
|
-
if (sets.length > 0) {
|
|
966
|
-
sets.push("updatedAt = ?");
|
|
967
|
-
params.push(now);
|
|
968
|
-
params.push(id);
|
|
969
|
-
await this.db
|
|
970
|
-
.prepare(`UPDATE access_policies SET ${sets.join(", ")} WHERE id = ?`)
|
|
971
|
-
.bind(...params)
|
|
972
|
-
.run();
|
|
973
|
-
}
|
|
974
|
-
invalidateRulesCache();
|
|
975
|
-
return (await this.getAccessPolicy(id));
|
|
976
|
-
}
|
|
977
|
-
async deleteAccessPolicy(id) {
|
|
978
|
-
await this.ensureSchema();
|
|
979
|
-
await this.db.prepare("DELETE FROM access_policies WHERE id = ?").bind(id).run();
|
|
980
|
-
invalidateRulesCache();
|
|
981
|
-
}
|
|
982
|
-
async getAccessPolicyRuleCount(id) {
|
|
983
|
-
const row = await this.db
|
|
984
|
-
.prepare("SELECT COUNT(*) as count FROM security_rules WHERE accessPolicyId = ?")
|
|
985
|
-
.bind(id)
|
|
986
|
-
.first();
|
|
987
|
-
return Number(row?.count ?? 0);
|
|
988
|
-
}
|
|
989
|
-
// ─── Security rules: CRUD ─────────────────────────────────────────────
|
|
990
|
-
async getSecurityRules(instanceDid) {
|
|
991
|
-
await this.ensureSchema();
|
|
992
|
-
let sql = `SELECT r.*, p.name as accessPolicyName
|
|
993
|
-
FROM security_rules r
|
|
994
|
-
LEFT JOIN access_policies p ON r.accessPolicyId = p.id`;
|
|
995
|
-
const params = [];
|
|
996
|
-
if (instanceDid !== undefined) {
|
|
997
|
-
sql += ` WHERE r.instance_did = ?`;
|
|
998
|
-
params.push(instanceDid);
|
|
999
|
-
}
|
|
1000
|
-
else {
|
|
1001
|
-
sql += ` WHERE r.instance_did IS NULL`;
|
|
1002
|
-
}
|
|
1003
|
-
sql += ` ORDER BY CASE WHEN r.id = 'default' THEN 1 ELSE 0 END, r.priority ASC`;
|
|
1004
|
-
const rows = await this.db
|
|
1005
|
-
.prepare(sql)
|
|
1006
|
-
.bind(...params)
|
|
1007
|
-
.all();
|
|
1008
|
-
return (rows.results ?? []).map((r) => ({
|
|
1009
|
-
id: r.id,
|
|
1010
|
-
pathPattern: r.pathPattern,
|
|
1011
|
-
priority: r.priority,
|
|
1012
|
-
accessPolicyId: r.accessPolicyId,
|
|
1013
|
-
accessPolicyName: r.accessPolicyName ?? "",
|
|
1014
|
-
enabled: r.enabled === 1,
|
|
1015
|
-
remark: r.remark,
|
|
1016
|
-
createdAt: r.createdAt,
|
|
1017
|
-
updatedAt: r.updatedAt,
|
|
1018
|
-
instanceDid: r.instance_did ?? null,
|
|
1019
|
-
}));
|
|
1020
|
-
}
|
|
1021
|
-
async getSecurityRule(id) {
|
|
1022
|
-
await this.ensureSchema();
|
|
1023
|
-
const r = await this.db
|
|
1024
|
-
.prepare(`SELECT r.*, p.name as accessPolicyName
|
|
1025
|
-
FROM security_rules r
|
|
1026
|
-
LEFT JOIN access_policies p ON r.accessPolicyId = p.id
|
|
1027
|
-
WHERE r.id = ?`)
|
|
1028
|
-
.bind(id)
|
|
1029
|
-
.first();
|
|
1030
|
-
if (!r)
|
|
1031
|
-
return null;
|
|
1032
|
-
return {
|
|
1033
|
-
id: r.id,
|
|
1034
|
-
pathPattern: r.pathPattern,
|
|
1035
|
-
priority: r.priority,
|
|
1036
|
-
accessPolicyId: r.accessPolicyId,
|
|
1037
|
-
accessPolicyName: r.accessPolicyName ?? "",
|
|
1038
|
-
enabled: r.enabled === 1,
|
|
1039
|
-
remark: r.remark,
|
|
1040
|
-
createdAt: r.createdAt,
|
|
1041
|
-
updatedAt: r.updatedAt,
|
|
1042
|
-
instanceDid: r.instance_did ?? null,
|
|
1043
|
-
};
|
|
1044
|
-
}
|
|
1045
|
-
async createSecurityRule(input) {
|
|
1046
|
-
await this.ensureSchema();
|
|
1047
|
-
const id = crypto.randomUUID();
|
|
1048
|
-
const now = new Date().toISOString();
|
|
1049
|
-
await this.db
|
|
1050
|
-
.prepare(`INSERT INTO security_rules (id, pathPattern, priority, accessPolicyId, enabled, remark, instance_did, createdAt, updatedAt)
|
|
1051
|
-
VALUES (?, ?, ?, ?, 1, ?, ?, ?, ?)`)
|
|
1052
|
-
.bind(id, input.pathPattern, input.priority ?? 0, input.accessPolicyId, input.remark ?? "", input.instanceDid ?? null, now, now)
|
|
1053
|
-
.run();
|
|
1054
|
-
invalidateRulesCache();
|
|
1055
|
-
return (await this.getSecurityRule(id));
|
|
1056
|
-
}
|
|
1057
|
-
async updateSecurityRule(id, input) {
|
|
1058
|
-
await this.ensureSchema();
|
|
1059
|
-
const now = new Date().toISOString();
|
|
1060
|
-
const sets = [];
|
|
1061
|
-
const params = [];
|
|
1062
|
-
if (input.pathPattern !== undefined) {
|
|
1063
|
-
sets.push("pathPattern = ?");
|
|
1064
|
-
params.push(input.pathPattern);
|
|
1065
|
-
}
|
|
1066
|
-
if (input.accessPolicyId !== undefined) {
|
|
1067
|
-
sets.push("accessPolicyId = ?");
|
|
1068
|
-
params.push(input.accessPolicyId);
|
|
1069
|
-
}
|
|
1070
|
-
if (input.priority !== undefined) {
|
|
1071
|
-
sets.push("priority = ?");
|
|
1072
|
-
params.push(input.priority);
|
|
1073
|
-
}
|
|
1074
|
-
if (input.enabled !== undefined) {
|
|
1075
|
-
sets.push("enabled = ?");
|
|
1076
|
-
params.push(input.enabled ? 1 : 0);
|
|
1077
|
-
}
|
|
1078
|
-
if (input.remark !== undefined) {
|
|
1079
|
-
sets.push("remark = ?");
|
|
1080
|
-
params.push(input.remark);
|
|
1081
|
-
}
|
|
1082
|
-
if (sets.length > 0) {
|
|
1083
|
-
sets.push("updatedAt = ?");
|
|
1084
|
-
params.push(now);
|
|
1085
|
-
params.push(id);
|
|
1086
|
-
await this.db
|
|
1087
|
-
.prepare(`UPDATE security_rules SET ${sets.join(", ")} WHERE id = ?`)
|
|
1088
|
-
.bind(...params)
|
|
1089
|
-
.run();
|
|
1090
|
-
}
|
|
1091
|
-
invalidateRulesCache();
|
|
1092
|
-
return (await this.getSecurityRule(id));
|
|
1093
|
-
}
|
|
1094
|
-
async deleteSecurityRule(id) {
|
|
1095
|
-
await this.ensureSchema();
|
|
1096
|
-
await this.db.prepare("DELETE FROM security_rules WHERE id = ?").bind(id).run();
|
|
1097
|
-
invalidateRulesCache();
|
|
1098
|
-
}
|
|
1099
|
-
// ─── Membership operations ──────────────────────────────────────────────
|
|
1100
|
-
async createMembership(userDid, instanceDid, role, invitedBy) {
|
|
1101
|
-
await this.ensureSchema();
|
|
1102
|
-
await this.db
|
|
1103
|
-
.prepare(`INSERT INTO memberships (user_did, instance_did, role, invited_by) VALUES (?, ?, ?, ?)`)
|
|
1104
|
-
.bind(userDid, instanceDid, role, invitedBy ?? null)
|
|
1105
|
-
.run();
|
|
1106
|
-
invalidateMembership(userDid, instanceDid);
|
|
1107
|
-
}
|
|
1108
|
-
async getMembership(userDid, instanceDid) {
|
|
1109
|
-
const cacheKey = membershipKey(userDid, instanceDid);
|
|
1110
|
-
const cached = _membershipCache.get(cacheKey);
|
|
1111
|
-
if (cached && Date.now() - cached.at < RULES_CACHE_TTL) {
|
|
1112
|
-
return cached.result;
|
|
1113
|
-
}
|
|
1114
|
-
await this.ensureSchema();
|
|
1115
|
-
const result = await this.db
|
|
1116
|
-
.prepare("SELECT * FROM memberships WHERE user_did = ? AND instance_did = ?")
|
|
1117
|
-
.bind(userDid, instanceDid)
|
|
1118
|
-
.first();
|
|
1119
|
-
_membershipCache.set(cacheKey, { result: result ?? null, at: Date.now() });
|
|
1120
|
-
return result ?? null;
|
|
1121
|
-
}
|
|
1122
|
-
async listMemberships(instanceDid) {
|
|
1123
|
-
await this.ensureSchema();
|
|
1124
|
-
const rows = await this.db
|
|
1125
|
-
.prepare("SELECT * FROM memberships WHERE instance_did = ? ORDER BY joined_at ASC")
|
|
1126
|
-
.bind(instanceDid)
|
|
1127
|
-
.all();
|
|
1128
|
-
return rows.results ?? [];
|
|
1129
|
-
}
|
|
1130
|
-
async listMembershipsWithUserInfo(instanceDid) {
|
|
1131
|
-
await this.ensureSchema();
|
|
1132
|
-
const rows = await this.db
|
|
1133
|
-
.prepare(`SELECT m.user_did, m.instance_did, m.role, m.invited_by, m.joined_at,
|
|
1134
|
-
u.fullName, u.email, u.avatar, COALESCE(u.approved, 1) as approved
|
|
1135
|
-
FROM memberships m
|
|
1136
|
-
LEFT JOIN users u ON m.user_did = u.did
|
|
1137
|
-
WHERE m.instance_did = ?
|
|
1138
|
-
ORDER BY
|
|
1139
|
-
CASE m.role WHEN 'owner' THEN 0 WHEN 'admin' THEN 1 WHEN 'member' THEN 2 ELSE 3 END,
|
|
1140
|
-
m.joined_at ASC`)
|
|
1141
|
-
.bind(instanceDid)
|
|
1142
|
-
.all();
|
|
1143
|
-
return rows.results ?? [];
|
|
1144
|
-
}
|
|
1145
|
-
async updateMembershipRole(userDid, instanceDid, role) {
|
|
1146
|
-
await this.ensureSchema();
|
|
1147
|
-
await this.db
|
|
1148
|
-
.prepare("UPDATE memberships SET role = ? WHERE user_did = ? AND instance_did = ?")
|
|
1149
|
-
.bind(role, userDid, instanceDid)
|
|
1150
|
-
.run();
|
|
1151
|
-
invalidateMembership(userDid, instanceDid);
|
|
1152
|
-
}
|
|
1153
|
-
async deleteMembership(userDid, instanceDid) {
|
|
1154
|
-
await this.ensureSchema();
|
|
1155
|
-
await this.db
|
|
1156
|
-
.prepare("DELETE FROM memberships WHERE user_did = ? AND instance_did = ?")
|
|
1157
|
-
.bind(userDid, instanceDid)
|
|
1158
|
-
.run();
|
|
1159
|
-
invalidateMembership(userDid, instanceDid);
|
|
1160
|
-
}
|
|
1161
|
-
async deleteMembershipsByInstance(instanceDid) {
|
|
1162
|
-
await this.ensureSchema();
|
|
1163
|
-
await this.db.prepare("DELETE FROM memberships WHERE instance_did = ?").bind(instanceDid).run();
|
|
1164
|
-
invalidateMembershipsByInstance(instanceDid);
|
|
1165
|
-
}
|
|
1166
|
-
// ─── Settings CRUD ─────────────────────────────────────────────────────
|
|
1167
|
-
async getSetting(instanceDid, key) {
|
|
1168
|
-
await this.ensureSchema();
|
|
1169
|
-
const row = await this.db
|
|
1170
|
-
.prepare("SELECT value FROM settings WHERE instance_did = ? AND key = ?")
|
|
1171
|
-
.bind(instanceDid, key)
|
|
1172
|
-
.first();
|
|
1173
|
-
return row?.value ?? null;
|
|
1174
|
-
}
|
|
1175
|
-
async setSetting(instanceDid, key, value) {
|
|
1176
|
-
await this.ensureSchema();
|
|
1177
|
-
await this.db
|
|
1178
|
-
.prepare(`INSERT INTO settings (instance_did, key, value, updated_at)
|
|
1179
|
-
VALUES (?, ?, ?, datetime('now'))
|
|
1180
|
-
ON CONFLICT(instance_did, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`)
|
|
1181
|
-
.bind(instanceDid, key, value)
|
|
1182
|
-
.run();
|
|
1183
|
-
}
|
|
1184
|
-
async listSettings(instanceDid) {
|
|
1185
|
-
await this.ensureSchema();
|
|
1186
|
-
const rows = await this.db
|
|
1187
|
-
.prepare("SELECT key, value, updated_at FROM settings WHERE instance_did = ? ORDER BY key")
|
|
1188
|
-
.bind(instanceDid)
|
|
1189
|
-
.all();
|
|
1190
|
-
return rows.results ?? [];
|
|
1191
|
-
}
|
|
1192
|
-
async deleteSetting(instanceDid, key) {
|
|
1193
|
-
await this.ensureSchema();
|
|
1194
|
-
await this.db
|
|
1195
|
-
.prepare("DELETE FROM settings WHERE instance_did = ? AND key = ?")
|
|
1196
|
-
.bind(instanceDid, key)
|
|
1197
|
-
.run();
|
|
1198
|
-
}
|
|
1199
|
-
// ─── Verify codes (Email Login) ────────────────────────────────────────
|
|
1200
|
-
async createVerifyCode(code, subject, purpose) {
|
|
1201
|
-
await this.ensureSchema();
|
|
1202
|
-
const result = await this.db
|
|
1203
|
-
.prepare("INSERT INTO verify_codes (code, subject, purpose) VALUES (?, ?, ?)")
|
|
1204
|
-
.bind(code, subject, purpose)
|
|
1205
|
-
.run();
|
|
1206
|
-
// D1 returns last_row_id in meta
|
|
1207
|
-
return result.meta.last_row_id ?? 0;
|
|
1208
|
-
}
|
|
1209
|
-
async consumeVerifyCode(code) {
|
|
1210
|
-
await this.ensureSchema();
|
|
1211
|
-
const row = await this.db
|
|
1212
|
-
.prepare(`SELECT id, subject FROM verify_codes
|
|
1213
|
-
WHERE code = ? AND verified = 0
|
|
1214
|
-
AND created_at > datetime('now', '-30 minutes')`)
|
|
1215
|
-
.bind(code)
|
|
1216
|
-
.first();
|
|
1217
|
-
if (!row)
|
|
1218
|
-
return null;
|
|
1219
|
-
await this.db
|
|
1220
|
-
.prepare("UPDATE verify_codes SET verified = 1, verified_at = datetime('now') WHERE code = ?")
|
|
1221
|
-
.bind(code)
|
|
1222
|
-
.run();
|
|
1223
|
-
return row;
|
|
1224
|
-
}
|
|
1225
|
-
async isVerifyCodeSent(subject) {
|
|
1226
|
-
await this.ensureSchema();
|
|
1227
|
-
const row = await this.db
|
|
1228
|
-
.prepare(`SELECT 1 FROM verify_codes
|
|
1229
|
-
WHERE subject = ? AND sent = 1
|
|
1230
|
-
AND sent_at > datetime('now', '-1 minute')`)
|
|
1231
|
-
.bind(subject)
|
|
1232
|
-
.first();
|
|
1233
|
-
return !!row;
|
|
1234
|
-
}
|
|
1235
|
-
async markVerifyCodeSent(code) {
|
|
1236
|
-
await this.ensureSchema();
|
|
1237
|
-
await this.db
|
|
1238
|
-
.prepare("UPDATE verify_codes SET sent = 1, sent_at = datetime('now') WHERE code = ?")
|
|
1239
|
-
.bind(code)
|
|
1240
|
-
.run();
|
|
1241
|
-
}
|
|
1242
|
-
async purgeExpiredVerifyCodes() {
|
|
1243
|
-
await this.ensureSchema();
|
|
1244
|
-
await this.db
|
|
1245
|
-
.prepare("DELETE FROM verify_codes WHERE created_at < datetime('now', '-1 hour')")
|
|
1246
|
-
.run();
|
|
1247
|
-
}
|
|
1248
|
-
// ─── Login tickets (Federation) ────────────────────────────────────────
|
|
1249
|
-
async createLoginTicket(ticket, did, targetOrigin, ttlSeconds = 300) {
|
|
1250
|
-
await this.ensureSchema();
|
|
1251
|
-
await this.db
|
|
1252
|
-
.prepare(`INSERT INTO login_tickets (ticket, did, target_origin, expires_at)
|
|
1253
|
-
VALUES (?, ?, ?, datetime('now', '+' || ? || ' seconds'))`)
|
|
1254
|
-
.bind(ticket, did, targetOrigin || null, ttlSeconds)
|
|
1255
|
-
.run();
|
|
1256
|
-
}
|
|
1257
|
-
async getLoginTicket(ticket) {
|
|
1258
|
-
await this.ensureSchema();
|
|
1259
|
-
return this.db
|
|
1260
|
-
.prepare(`SELECT ticket, did, target_origin FROM login_tickets
|
|
1261
|
-
WHERE ticket = ? AND expires_at > datetime('now')`)
|
|
1262
|
-
.bind(ticket)
|
|
1263
|
-
.first();
|
|
1264
|
-
}
|
|
1265
|
-
async deleteLoginTicket(ticket) {
|
|
1266
|
-
await this.ensureSchema();
|
|
1267
|
-
await this.db.prepare("DELETE FROM login_tickets WHERE ticket = ?").bind(ticket).run();
|
|
1268
|
-
}
|
|
1269
|
-
async purgeExpiredLoginTickets() {
|
|
1270
|
-
await this.ensureSchema();
|
|
1271
|
-
await this.db.prepare("DELETE FROM login_tickets WHERE expires_at < datetime('now')").run();
|
|
1272
|
-
}
|
|
1273
|
-
// ─── Instance default rule seeding ──────────────────────────────────────
|
|
1274
|
-
async seedInstanceDefaults(instanceDid) {
|
|
1275
|
-
await this.ensureSchema();
|
|
1276
|
-
const now = new Date().toISOString();
|
|
1277
|
-
// Seed instance-scoped copies of built-in policies
|
|
1278
|
-
const policies = [
|
|
1279
|
-
{
|
|
1280
|
-
id: `public-${instanceDid}`,
|
|
1281
|
-
name: "Public",
|
|
1282
|
-
description: "Anyone can access, no login required",
|
|
1283
|
-
roles: null,
|
|
1284
|
-
reverse: 0,
|
|
1285
|
-
},
|
|
1286
|
-
{
|
|
1287
|
-
id: `invited-only-${instanceDid}`,
|
|
1288
|
-
name: "Invited Only",
|
|
1289
|
-
description: "Any logged-in user can access",
|
|
1290
|
-
roles: "[]",
|
|
1291
|
-
reverse: 1,
|
|
1292
|
-
},
|
|
1293
|
-
{
|
|
1294
|
-
id: `admin-only-${instanceDid}`,
|
|
1295
|
-
name: "Admin Only",
|
|
1296
|
-
description: "Only owner and admin can access",
|
|
1297
|
-
roles: '["owner","admin"]',
|
|
1298
|
-
reverse: 0,
|
|
1299
|
-
},
|
|
1300
|
-
{
|
|
1301
|
-
id: `owner-only-${instanceDid}`,
|
|
1302
|
-
name: "Owner Only",
|
|
1303
|
-
description: "Only the owner can access",
|
|
1304
|
-
roles: '["owner"]',
|
|
1305
|
-
reverse: 0,
|
|
1306
|
-
},
|
|
1307
|
-
];
|
|
1308
|
-
await this.db.batch(policies.map((p) => this.db
|
|
1309
|
-
.prepare(`INSERT OR IGNORE INTO access_policies (id, name, description, roles, reverse, isProtected, instance_did, createdAt, updatedAt)
|
|
1310
|
-
VALUES (?, ?, ?, ?, ?, 1, ?, ?, ?)`)
|
|
1311
|
-
.bind(p.id, p.name, p.description, p.roles, p.reverse, instanceDid, now, now)));
|
|
1312
|
-
// Seed instance default rule referencing the instance-scoped public policy
|
|
1313
|
-
const ruleId = `default-${instanceDid}`;
|
|
1314
|
-
await this.db
|
|
1315
|
-
.prepare(`INSERT OR IGNORE INTO security_rules
|
|
1316
|
-
(id, pathPattern, priority, accessPolicyId, enabled, remark, instance_did, createdAt, updatedAt)
|
|
1317
|
-
VALUES (?, '*', 0, ?, 1, 'Instance default: public access', ?, ?, ?)`)
|
|
1318
|
-
.bind(ruleId, `public-${instanceDid}`, instanceDid, now, now)
|
|
1319
|
-
.run();
|
|
1320
|
-
invalidateRulesCache();
|
|
1321
|
-
}
|
|
1322
|
-
// ─── Instance-scoped rule query ────────────────────────────────────────
|
|
1323
|
-
async getActiveRulesForInstance(instanceDid) {
|
|
1324
|
-
const cached = _instanceRulesCache.get(instanceDid);
|
|
1325
|
-
if (cached && Date.now() - cached.at < RULES_CACHE_TTL) {
|
|
1326
|
-
return cached.rules;
|
|
1327
|
-
}
|
|
1328
|
-
await this.ensureSchema();
|
|
1329
|
-
const rows = await this.db
|
|
1330
|
-
.prepare(`SELECT r.id, r.pathPattern, r.priority, p.roles, p.reverse, r.enabled
|
|
1331
|
-
FROM security_rules r
|
|
1332
|
-
JOIN access_policies p ON r.accessPolicyId = p.id
|
|
1333
|
-
WHERE r.enabled = 1
|
|
1334
|
-
AND (r.instance_did = ?1 OR r.instance_did IS NULL)
|
|
1335
|
-
AND (p.instance_did = ?1 OR p.instance_did IS NULL)
|
|
1336
|
-
ORDER BY
|
|
1337
|
-
CASE WHEN r.instance_did IS NOT NULL THEN 0 ELSE 1 END,
|
|
1338
|
-
r.priority DESC`)
|
|
1339
|
-
.bind(instanceDid)
|
|
1340
|
-
.all();
|
|
1341
|
-
const rules = rows.results ?? [];
|
|
1342
|
-
_instanceRulesCache.set(instanceDid, { rules, at: Date.now() });
|
|
1343
|
-
return rules;
|
|
1344
|
-
}
|
|
1345
|
-
/**
|
|
1346
|
-
* Optimized query for enforcement: returns all enabled rules with inlined policy data.
|
|
1347
|
-
* Results are cached per-isolate with TTL + write-through invalidation.
|
|
1348
|
-
*/
|
|
1349
|
-
async getActiveRulesWithPolicies() {
|
|
1350
|
-
if (_rulesCache && Date.now() - _rulesCacheAt < RULES_CACHE_TTL) {
|
|
1351
|
-
return _rulesCache;
|
|
1352
|
-
}
|
|
1353
|
-
await this.ensureSchema();
|
|
1354
|
-
const rows = await this.db
|
|
1355
|
-
.prepare(`SELECT r.id, r.pathPattern, r.priority, p.roles, p.reverse, r.enabled
|
|
1356
|
-
FROM security_rules r
|
|
1357
|
-
JOIN access_policies p ON r.accessPolicyId = p.id
|
|
1358
|
-
WHERE r.enabled = 1
|
|
1359
|
-
ORDER BY CASE WHEN r.id = 'default' THEN 1 ELSE 0 END, r.priority ASC`)
|
|
1360
|
-
.all();
|
|
1361
|
-
_rulesCache = rows.results ?? [];
|
|
1362
|
-
_rulesCacheAt = Date.now();
|
|
1363
|
-
return _rulesCache;
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
//# sourceMappingURL=store.js.map
|