@oimlsmart/platform-server 0.1.0
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 +92 -0
- package/migrations/0001_init.sql +69 -0
- package/migrations/0002_identity.sql +24 -0
- package/migrations/0003_federation_peers.sql +21 -0
- package/migrations/0003_users_rbac.sql +8 -0
- package/migrations/0004_oidc_op.sql +61 -0
- package/migrations/0005_upstream_providers.sql +34 -0
- package/migrations/0006_op_accounts.sql +28 -0
- package/migrations/0007_org_join_requests.sql +26 -0
- package/migrations/0008_op_client_roles.sql +19 -0
- package/migrations/0009_account_console.sql +32 -0
- package/migrations/0009_sso_states.sql +13 -0
- package/migrations/0010_notify_events.sql +23 -0
- package/migrations/0011_op_launch.sql +18 -0
- package/migrations/0011_org_memberships.sql +62 -0
- package/migrations/0012_notify_subscriptions.sql +57 -0
- package/migrations/0012_strong_auth.sql +109 -0
- package/migrations/0013_org_registry.sql +53 -0
- package/migrations/0014_notify_inbox.sql +34 -0
- package/migrations/0015_certificate_holder_attribution.sql +63 -0
- package/migrations/0016_instrument_registrations.sql +78 -0
- package/package.json +52 -0
- package/src/client-info.ts +25 -0
- package/src/context.ts +31 -0
- package/src/github.ts +284 -0
- package/src/mailer.ts +309 -0
- package/src/oidc.ts +369 -0
- package/src/profile/node.ts +83 -0
- package/src/profile.ts +582 -0
- package/src/rbac/node.ts +42 -0
- package/src/rbac.ts +53 -0
- package/src/session.ts +45 -0
- package/src/store/d1.ts +2850 -0
- package/src/store/sqlite/entities.ts +71 -0
- package/src/store/sqlite/events.ts +82 -0
- package/src/store/sqlite/factors-store.ts +348 -0
- package/src/store/sqlite/notify.ts +247 -0
- package/src/store/sqlite/op-accounts-store.ts +470 -0
- package/src/store/sqlite/op-store.ts +280 -0
- package/src/store/sqlite/schema.sql +745 -0
- package/src/store/sqlite/store.ts +1390 -0
- package/src/store/sqlite/upstream-store.ts +148 -0
- package/src/store/sqlite.ts +1027 -0
- package/src/store.ts +1826 -0
- package/src/vocab/index.ts +12 -0
- package/src/vocab/permissions.ts +398 -0
- package/src/vocab/rbac.ts +281 -0
- package/src/vocab/roles.ts +162 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
2
|
+
// The upstream providers' SQLite half (TODO.identity/08) — the sync
|
|
3
|
+
// implementations behind the ServerStore upstream methods
|
|
4
|
+
// (sqlite-server-store.ts delegates here one-for-one, mirroring
|
|
5
|
+
// op-store.ts's role for the OP domain).
|
|
6
|
+
//
|
|
7
|
+
// Two tables:
|
|
8
|
+
// identity_providers — the upstream registry (GitHub + Google + Apple
|
|
9
|
+
// + Entra + generic OIDC; the client secret is NEVER stored, only
|
|
10
|
+
// the env reference);
|
|
11
|
+
// identity_links — the linked identities (TODO.identity/02's shape,
|
|
12
|
+
// landed additively here): THE match rule for an upstream sign-in
|
|
13
|
+
// is (provider, provider_account_id) — NEVER email alone.
|
|
14
|
+
//
|
|
15
|
+
// NODE-ONLY: better-sqlite3 through ./store's getDb. The Worker bundle
|
|
16
|
+
// never sees this module (the D1 store implements the same surface in
|
|
17
|
+
// d1-store.ts).
|
|
18
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
19
|
+
|
|
20
|
+
import { randomUUID } from 'crypto'
|
|
21
|
+
import { getDb } from './store'
|
|
22
|
+
import type { IdentityLink, IdentityProvider } from '../../store'
|
|
23
|
+
|
|
24
|
+
function toIdentityProvider(row: Record<string, unknown>): IdentityProvider {
|
|
25
|
+
return {
|
|
26
|
+
id: row.id as string,
|
|
27
|
+
kind: row.kind as IdentityProvider['kind'],
|
|
28
|
+
displayName: row.display_name as string,
|
|
29
|
+
brandMark: (row.brand_mark as string | null) ?? null,
|
|
30
|
+
issuer: (row.issuer as string | null) ?? null,
|
|
31
|
+
clientId: row.client_id as string,
|
|
32
|
+
clientSecretRef: (row.client_secret_ref as string | null) ?? null,
|
|
33
|
+
scopes: (row.scopes as string | null) ?? null,
|
|
34
|
+
enabled: row.enabled === 1,
|
|
35
|
+
createdAt: row.created_at as string,
|
|
36
|
+
createdBy: (row.created_by as string | null) ?? null,
|
|
37
|
+
updatedAt: (row.updated_at as string | null) ?? null,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function listIdentityProviders(): IdentityProvider[] {
|
|
42
|
+
const rows = getDb().prepare('SELECT * FROM identity_providers ORDER BY created_at, id').all() as Array<Record<string, unknown>>
|
|
43
|
+
return rows.map(toIdentityProvider)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function getIdentityProvider(id: string): IdentityProvider | null {
|
|
47
|
+
const row = getDb().prepare('SELECT * FROM identity_providers WHERE id = ?').get(id) as Record<string, unknown> | undefined
|
|
48
|
+
return row ? toIdentityProvider(row) : null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function upsertIdentityProvider(input: {
|
|
52
|
+
id: string
|
|
53
|
+
kind: IdentityProvider['kind']
|
|
54
|
+
displayName: string
|
|
55
|
+
brandMark?: string | null
|
|
56
|
+
issuer?: string | null
|
|
57
|
+
clientId: string
|
|
58
|
+
clientSecretRef?: string | null
|
|
59
|
+
scopes?: string | null
|
|
60
|
+
enabled?: boolean
|
|
61
|
+
createdBy?: string | null
|
|
62
|
+
}): IdentityProvider {
|
|
63
|
+
getDb().prepare(`
|
|
64
|
+
INSERT INTO identity_providers
|
|
65
|
+
(id, kind, display_name, brand_mark, issuer, client_id, client_secret_ref, scopes, enabled, created_by)
|
|
66
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
67
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
68
|
+
kind = excluded.kind,
|
|
69
|
+
display_name = excluded.display_name,
|
|
70
|
+
brand_mark = excluded.brand_mark,
|
|
71
|
+
issuer = excluded.issuer,
|
|
72
|
+
client_id = excluded.client_id,
|
|
73
|
+
client_secret_ref = excluded.client_secret_ref,
|
|
74
|
+
scopes = excluded.scopes,
|
|
75
|
+
enabled = excluded.enabled,
|
|
76
|
+
updated_at = datetime('now')
|
|
77
|
+
`).run(
|
|
78
|
+
input.id,
|
|
79
|
+
input.kind,
|
|
80
|
+
input.displayName,
|
|
81
|
+
input.brandMark ?? null,
|
|
82
|
+
input.issuer ?? null,
|
|
83
|
+
input.clientId,
|
|
84
|
+
input.clientSecretRef ?? null,
|
|
85
|
+
input.scopes ?? null,
|
|
86
|
+
// Disabled by default (the schema's DEFAULT 0): a provider becomes
|
|
87
|
+
// visible to the login page only by a deliberate enable.
|
|
88
|
+
input.enabled ? 1 : 0,
|
|
89
|
+
input.createdBy ?? null,
|
|
90
|
+
)
|
|
91
|
+
return getIdentityProvider(input.id)!
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function setIdentityProviderEnabled(id: string, enabled: boolean): IdentityProvider | null {
|
|
95
|
+
const res = getDb().prepare("UPDATE identity_providers SET enabled = ?, updated_at = datetime('now') WHERE id = ?")
|
|
96
|
+
.run(enabled ? 1 : 0, id)
|
|
97
|
+
return res.changes > 0 ? getIdentityProvider(id) : null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function deleteIdentityProvider(id: string): boolean {
|
|
101
|
+
const res = getDb().prepare('DELETE FROM identity_providers WHERE id = ?').run(id)
|
|
102
|
+
return res.changes > 0
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── the linked identities ────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
function toIdentityLink(row: Record<string, unknown>): IdentityLink {
|
|
108
|
+
return {
|
|
109
|
+
id: row.id as string,
|
|
110
|
+
userId: row.user_id as string,
|
|
111
|
+
provider: row.provider as string,
|
|
112
|
+
providerAccountId: row.provider_account_id as string,
|
|
113
|
+
linkedAt: row.linked_at as string,
|
|
114
|
+
linkedBy: (row.linked_by as string | null) ?? null,
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function listIdentityLinks(userId: string): IdentityLink[] {
|
|
119
|
+
const rows = getDb().prepare('SELECT * FROM identity_links WHERE user_id = ? ORDER BY linked_at, provider').all(userId) as Array<Record<string, unknown>>
|
|
120
|
+
return rows.map(toIdentityLink)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function findIdentityLink(provider: string, providerAccountId: string): IdentityLink | null {
|
|
124
|
+
const row = getDb().prepare('SELECT * FROM identity_links WHERE provider = ? AND provider_account_id = ?')
|
|
125
|
+
.get(provider, providerAccountId) as Record<string, unknown> | undefined
|
|
126
|
+
return row ? toIdentityLink(row) : null
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Create the link; NULL on the UNIQUE(provider, provider_account_id)
|
|
130
|
+
* conflict — the pair is already linked (to any account). */
|
|
131
|
+
export function createIdentityLink(input: {
|
|
132
|
+
userId: string
|
|
133
|
+
provider: string
|
|
134
|
+
providerAccountId: string
|
|
135
|
+
linkedBy?: string | null
|
|
136
|
+
}): IdentityLink | null {
|
|
137
|
+
const id = randomUUID()
|
|
138
|
+
const res = getDb().prepare(
|
|
139
|
+
'INSERT OR IGNORE INTO identity_links (id, user_id, provider, provider_account_id, linked_by) VALUES (?, ?, ?, ?, ?)',
|
|
140
|
+
).run(id, input.userId, input.provider, input.providerAccountId, input.linkedBy ?? null)
|
|
141
|
+
if (res.changes === 0) return null
|
|
142
|
+
return findIdentityLink(input.provider, input.providerAccountId)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function deleteIdentityLink(userId: string, provider: string): boolean {
|
|
146
|
+
const res = getDb().prepare('DELETE FROM identity_links WHERE user_id = ? AND provider = ?').run(userId, provider)
|
|
147
|
+
return res.changes > 0
|
|
148
|
+
}
|