-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathsys-scim-provider.object.ts
More file actions
152 lines (140 loc) · 6.36 KB
/
Copy pathsys-scim-provider.object.ts
File metadata and controls
152 lines (140 loc) · 6.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
import { ObjectSchema, Field } from '@objectstack/spec/data';
/**
* sys_scim_provider — Registered SCIM 2.0 connection (@better-auth/scim)
*
* Backed by `@better-auth/scim`'s `scimProvider` model. Each row is a SCIM
* connection: a bearer token an external IdP (Okta / Entra / OneLogin) uses to
* **auto-provision / deprovision** THIS environment's users. The environment is
* the SCIM **Service Provider** (the receiver); the IdP is the SCIM **client**
* (the sender). This is the paid Identity lifecycle (ADR-0071) — the mechanism
* is OPEN (here, framework `plugin-auth`); enablement is entitlement-gated by
* the cloud / EE license.
*
* `scim_token` holds the connection's bearer credential. With the plugin's
* `storeSCIMToken: 'hashed'` (the default this env wires) it stores only a
* HASH — the plaintext is returned exactly once at `/scim/generate-token`. Even
* so, treat this object as sensitive: it is read-only over the generic data API
* and the token is excluded from list views.
*
* All mutations route through @better-auth/scim's endpoints under
* `/api/v1/auth/scim/*` (generate-token / delete-provider-connection) and the
* SCIM 2.0 protocol under `/api/v1/auth/scim/v2/*`; the generic data layer is
* read-only (see `enable.apiMethods`).
*
* @namespace sys
*/
export const SysScimProvider = ObjectSchema.create({
name: 'sys_scim_provider',
label: 'SCIM Provider',
pluralLabel: 'SCIM Providers',
icon: 'users',
isSystem: true,
managedBy: 'better-auth',
// [ADR-0066 D3/④] Admin-only identity config carrying a live credential
// (`scim_token` — the bearer external IdPs authenticate provisioning calls
// with). Object-level capability gate, mirroring the sibling
// `sys_sso_provider`: ordinary members are denied entirely (without it, the
// `member_default` wildcard `'*': allowRead` would expose SCIM connections
// to every authenticated user). better-auth's own endpoints read via a
// system context, so SCIM provisioning is unaffected.
requiredPermissions: ['manage_platform_settings'],
// ADR-0010 §3.7 — managed by better-auth; tenants may not edit schema.
protection: {
lock: 'full',
reason: 'Identity table managed by better-auth (@better-auth/scim) — see ADR-0071.',
docsUrl: 'https://docs.objectstack.ai/adr/0010-metadata-protection',
},
description: 'SCIM 2.0 connections (bearer tokens) external IdPs use to provision/deprovision this environment\'s users',
displayNameField: 'provider_id',
nameField: 'provider_id', // [ADR-0079] canonical primary-title pointer (mirrors deprecated displayNameField)
titleFormat: '{provider_id}',
highlightFields: ['provider_id', 'organization_id'],
listViews: {
all: {
type: 'grid',
name: 'all',
label: 'All',
data: { provider: 'object', object: 'sys_scim_provider' },
// scim_token is intentionally excluded — never surface the credential.
columns: ['provider_id', 'organization_id', 'created_at'],
sort: [{ field: 'provider_id', order: 'asc' }],
pagination: { pageSize: 50 },
},
},
fields: {
id: Field.text({ label: 'ID', required: true, readonly: true, group: 'System' }),
provider_id: Field.text({
label: 'Provider ID',
required: true,
searchable: true,
maxLength: 255,
description: 'Stable SCIM provider identifier (e.g. "okta-scim")',
group: 'Identity',
}),
// `@better-auth/scim`'s uniqueness boundary for a connection:
// `<organizationId>:<providerId>`, declared `required: true, unique: true,
// returned: false` upstream and written on every provider insert. Same
// shape as `sys_team_member.membership_key` — a derived key the library
// owns end to end, never authored or read from the ObjectStack side.
// Provisioned here because the adapter writes the column; without it SCIM
// provider creation fails the moment SCIM is switched on. See #3653.
provider_key: Field.text({
label: 'Provider Key',
required: false,
readonly: true,
maxLength: 512,
description: 'Derived <organization>:<provider_id> uniqueness key maintained by @better-auth/scim; do not write directly.',
group: 'System',
}),
scim_token: Field.text({
label: 'SCIM Token (hash)',
required: false,
readonly: true,
maxLength: 1024,
description: 'Hashed bearer credential for this SCIM connection — the plaintext is shown once at generate-token. Sensitive; do not expose.',
group: 'Secret',
}),
organization_id: Field.text({
label: 'Organization',
required: false,
maxLength: 255,
description: 'Organization scope of this token (org-scoped tokens restrict provisioning to that org)',
group: 'System',
}),
user_id: Field.lookup('sys_user', {
label: 'Owned By',
required: false,
description: 'User who generated this token (when provider-ownership is enabled)',
group: 'System',
}),
created_at: Field.datetime({ label: 'Created At', defaultValue: 'NOW()', readonly: true, group: 'System' }),
updated_at: Field.datetime({ label: 'Updated At', defaultValue: 'NOW()', readonly: true, group: 'System' }),
},
indexes: [
{ fields: ['provider_id'], unique: true },
{ fields: ['organization_id'] },
{ fields: ['user_id'] },
// UNIQUE mirrors @better-auth/scim's own declaration. Nullable, so rows
// provisioned before the column existed admit repeated NULLs on sqlite /
// postgres / mysql.
//
// NOTE: upstream's boundary is `<organization>:<provider_id>`, i.e. the
// SAME provider_id in two different organizations is legal there, while
// the `provider_id` unique index above forbids it. That is a stricter
// constraint than the library assumes, not something this column changes —
// left as-is deliberately and raised separately (#3653) rather than
// relaxed here, since loosening a live uniqueness constraint is its own
// decision.
{ fields: ['provider_key'], unique: true },
],
enable: {
trackHistory: true,
searchable: false,
apiEnabled: true,
// Mutations + token issuance go through @better-auth/scim's endpoints
// under /api/v1/auth/scim/*; the generic data layer is read-only so the
// credential cannot be written/bypassed through it.
apiMethods: ['list'],
},
});