pgkiln 0.62.0 → 0.63.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/SECURITY.md +2 -0
- package/db/migrations/131_builder_sso.sql +69 -0
- package/docs/apex-feature-parity.md +1 -1
- package/docs/guide/03-builder.md +46 -0
- package/docs/guide/08-security.md +2 -1
- package/docs/guide/12-development.md +3 -0
- package/docs/guide/18-cli.md +1 -0
- package/package.json +1 -1
- package/public/builder.css +4 -0
- package/src/builder/builder-sso.ts +135 -0
- package/src/builder/i18n/de.ts +50 -0
- package/src/builder/i18n/en.ts +50 -0
- package/src/builder/i18n/es.ts +50 -0
- package/src/builder/i18n/fr.ts +50 -0
- package/src/builder/i18n/nl.ts +50 -0
- package/src/builder/i18n/pt.ts +50 -0
- package/src/builder/ldap.ts +2 -0
- package/src/builder/routes.ts +137 -21
- package/src/builder/security.ts +3 -1
- package/src/builder/ui.ts +2 -1
- package/src/builder/users.ts +2 -0
- package/src/builder/workspaces.ts +37 -3
- package/src/cli/main.ts +19 -5
- package/src/devsso.ts +149 -0
- package/src/runtime/routes.ts +14 -3
- package/src/saml.ts +13 -7
- package/src/sso.ts +37 -7
package/SECURITY.md
CHANGED
|
@@ -115,6 +115,7 @@ first version (MVP). Every finding below has a regression test in
|
|
|
115
115
|
| Two-factor sign-in for developers (0.60.0) | TOTP secrets (RFC 6238, 160 bits) are encrypted with `PGKILN_SECRET_KEY` and stored only after a code from the app confirms them; recovery codes (10 × 50 bits) are stored as SHA-256 and used up atomically; a code is accepted once (the last step is updated atomically) and only one 30-second step either side. The password step leaves only a pending sign-in in the session (5 minutes, no session for the developer yet); wrong codes count with wrong passwords for the per-user and per-IP lockout. Removing an app needs the password; another developer's factors can only be reset by an administrator, with a reason, which ends that developer's builder sessions. The require policy can only be turned on, and stays enforceable, with at least two administrators (or every one) holding an app; there is no setting, URL or variable that skips the second step: recovery is `pgkiln admin reset-2fa` / `set-2fa-policy off` on the server with the owner connection. Every enrolment, removal, reset, policy change and used recovery code is in the activity log; resets and policy changes are mailed to the administrators. The factor tables are closed to the runtime role. |
|
|
116
116
|
| Passkeys and trusted browsers (0.61.0) | Passkeys (WebAuthn) are checked in `src/webauthn.ts` without a library: a 32-byte one-time challenge per purpose (registration or sign-in) kept in the session for five minutes and used up on the first answer, the `clientDataJSON` type, challenge and origin (`PUBLIC_URL`'s, else the browser's own), the SHA-256 of the relying party id in the authenticator data, user presence, an ES256, EdDSA or RS256 signature over the authenticator data and the client data hash, and a signature counter that must go up (updated atomically, so a replayed or cloned key is refused). The CBOR reader takes only definite lengths, at most 16 levels and 1,000 entries. Credentials are public keys (no secret to leak, independent of the instance key); attestation is *none*. `PGKILN_RP_ID` is only honoured when it is the host or a parent domain of it. The sign-in options (the developer's credential ids) are only given after the password step, with the CSRF token. Trusted browsers (off by default) are a random 256-bit cookie token (`HttpOnly`, `SameSite=Lax`, `Secure` with `COOKIE_SECURE`, path `/builder`) of which only the SHA-256 is stored; the cookie is only looked at after a correct password, for that developer, and ends with its days, a new password, removing a factor, a reset, *Forget trusted browsers* or turning trust off (which removes every trusted browser at once). |
|
|
117
117
|
| Passkey sign-in without a password, workspace rule (0.62.0) | *Sign in with a passkey* asks the browser for any passkey of this site (empty allow list) with `userVerification: required`, and the server refuses an answer whose authenticator data lacks the user-verified flag, so the passkey alone stands for possession plus a fingerprint, face or PIN. The credential id is looked up for this relying party only; a user handle, when the browser sends one, must be the developer's (a SHA-256-derived handle, not the user name). The same one-time challenge, origin, RP ID hash, signature and counter checks as at the second step apply; failures are logged as `login_failed` with the developer's name when the key is known and count for the per-user and per-IP lockout. Administrators turn it off in Instance settings. A workspace's rule (administrators and its own administrators only, CSRF-checked) can only add a requirement; `pgkiln admin set-2fa-policy off` clears every workspace's rule. While a second factor is required of a developer, a sign-in with the default password leads only to the password change. |
|
|
118
|
+
| Single sign-on for developers (0.63.0) | A provider or directory signs developers in only with *builder* turned on. The flow is the applications' (state, PKCE, nonce, browser-binding cookie, ID token or SAML assertion checks) with a pending row without an application; developers are found by the provider's stable subject in `meta.developer_identity`, never by a name alone. An existing developer is linked by name only when the administrator allows it, once, never a break-glass administrator, and an e-mail user name only when verified; otherwise the developer links the identity on Security, which needs their session and CSRF token to start and the same browser to finish, and one identity can't be linked to two developers. Automatic accounts need the developers group when one is set and start as developers; the administrators group decides the role at every sign-in but never demotes a break-glass administrator. A workspace's single sign-on rule (administrators and its own administrators, CSRF-checked) refuses the pgkiln password and a passkey alone, after the password check so it reveals nothing; it can only be turned on while a break-glass administrator exists and by someone who could still sign in; the last break-glass administrator and a developer's last identity can't be removed while it applies. Failures are logged as `login_failed` with the reason. |
|
|
118
119
|
|
|
119
120
|
## Findings of the MVP review and their fixes
|
|
120
121
|
|
|
@@ -238,3 +239,4 @@ A second full review, mainly of what end users and API callers can reach. Regres
|
|
|
238
239
|
9. With header authentication: list only your proxy's addresses in `PGKILN_AUTH_HEADER_PROXIES`, make sure users can't reach pgkiln directly, and have the proxy remove any incoming copy of the header.
|
|
239
240
|
10. With database-account authentication: list exact roles or use a dedicated membership role, and limit what those roles may connect to in `pg_hba.conf` (they can reach the database with their password outside pgkiln too).
|
|
240
241
|
11. Turn on two-factor sign-in for the builder (Instance settings → Two-factor sign-in, at least for administrators) once two administrators have a passkey or an authenticator app; set `PUBLIC_URL` to the address developers use (passkeys belong to its host name), require it per workspace where the instance policy is too broad, keep *Trust a browser* short or at 0 on shared machines, keep `PGKILN_SECRET_KEY` backed up, and keep the recovery commands (`pgkiln admin reset-2fa`) for people with server access only.
|
|
242
|
+
12. With single sign-on for developers: leave *Link an existing developer with the same user name* off unless you trust the provider's user names, set a developers group for automatic accounts, make at least one administrator break-glass (with a second factor and a password kept safe) before a workspace requires single sign-on, and keep `pgkiln admin builder-sign-in password` for people with server access only.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- ---------------------------------------------------------------------
|
|
2
|
+
-- 131: signing in to the builder through an identity provider, with a
|
|
3
|
+
-- break-glass administrator (sprint 129, SEC-1; APEX: the workspace's
|
|
4
|
+
-- authentication scheme).
|
|
5
|
+
--
|
|
6
|
+
-- meta.auth_provider / meta.ldap_directory.builder: the provider (OpenID
|
|
7
|
+
-- Connect, SAML) or directory also signs developers in to the builder.
|
|
8
|
+
-- builder_auto_create: a person without a developer account gets one on
|
|
9
|
+
-- the first sign-in, when builder_group is empty or one of their groups;
|
|
10
|
+
-- builder_link_existing: a developer with the same username is linked on
|
|
11
|
+
-- the first sign-in (never a break-glass developer); otherwise developers
|
|
12
|
+
-- link the identity themselves on the Security page;
|
|
13
|
+
-- builder_admin_group: members are instance administrators (checked on
|
|
14
|
+
-- every sign-in; break-glass developers are never demoted).
|
|
15
|
+
-- meta.developer_identity: the identities linked to a developer, by the
|
|
16
|
+
-- provider's or directory's stable subject (as meta.account_identity and
|
|
17
|
+
-- meta.ldap_identity for application accounts).
|
|
18
|
+
-- meta.developer.break_glass: may still sign in with the pgkiln password
|
|
19
|
+
-- (and passkey) where a workspace requires single sign-on; for when the
|
|
20
|
+
-- identity provider is down or misconfigured.
|
|
21
|
+
-- meta.workspace.sso_only: the workspace's developers must sign in through
|
|
22
|
+
-- an identity provider or directory (break-glass developers excepted).
|
|
23
|
+
-- `pgkiln admin builder-sign-in password` clears it everywhere.
|
|
24
|
+
-- meta.sso_pending.app_id null: a sign-in to the builder; link_username: the
|
|
25
|
+
-- signed-in developer who links an identity from the Security page.
|
|
26
|
+
-- ---------------------------------------------------------------------
|
|
27
|
+
alter table meta.auth_provider
|
|
28
|
+
add column builder boolean not null default false,
|
|
29
|
+
add column builder_auto_create boolean not null default false,
|
|
30
|
+
add column builder_link_existing boolean not null default false,
|
|
31
|
+
add column builder_group text check (length(builder_group) between 1 and 200),
|
|
32
|
+
add column builder_admin_group text check (length(builder_admin_group) between 1 and 200);
|
|
33
|
+
|
|
34
|
+
alter table meta.ldap_directory
|
|
35
|
+
add column builder boolean not null default false,
|
|
36
|
+
add column builder_auto_create boolean not null default false,
|
|
37
|
+
add column builder_link_existing boolean not null default false,
|
|
38
|
+
add column builder_group text check (length(builder_group) between 1 and 200),
|
|
39
|
+
add column builder_admin_group text check (length(builder_admin_group) between 1 and 200);
|
|
40
|
+
|
|
41
|
+
comment on column meta.auth_provider.builder is 'Developers can sign in to the builder with this provider (131).';
|
|
42
|
+
comment on column meta.ldap_directory.builder is 'Developers can sign in to the builder with this directory (131).';
|
|
43
|
+
|
|
44
|
+
create table meta.developer_identity (
|
|
45
|
+
id serial primary key,
|
|
46
|
+
provider_id integer references meta.auth_provider (id) on delete cascade,
|
|
47
|
+
directory_id integer references meta.ldap_directory (id) on delete cascade,
|
|
48
|
+
subject text not null check (length(subject) between 1 and 500),
|
|
49
|
+
username text not null references meta.developer (username) on update cascade on delete cascade,
|
|
50
|
+
linked_at timestamptz not null default now(),
|
|
51
|
+
check ((provider_id is null) <> (directory_id is null))
|
|
52
|
+
);
|
|
53
|
+
create unique index developer_identity_provider_subject on meta.developer_identity (provider_id, subject) where provider_id is not null;
|
|
54
|
+
create unique index developer_identity_directory_subject on meta.developer_identity (directory_id, subject) where directory_id is not null;
|
|
55
|
+
create unique index developer_identity_provider_user on meta.developer_identity (provider_id, username) where provider_id is not null;
|
|
56
|
+
create unique index developer_identity_directory_user on meta.developer_identity (directory_id, username) where directory_id is not null;
|
|
57
|
+
create index developer_identity_username on meta.developer_identity (username);
|
|
58
|
+
|
|
59
|
+
comment on table meta.developer_identity is 'Identities at providers or directories linked to a developer, by stable subject (131).';
|
|
60
|
+
|
|
61
|
+
alter table meta.developer add column break_glass boolean not null default false;
|
|
62
|
+
comment on column meta.developer.break_glass is 'May sign in with the pgkiln password where a workspace requires single sign-on (131).';
|
|
63
|
+
|
|
64
|
+
alter table meta.workspace add column sso_only boolean not null default false;
|
|
65
|
+
comment on column meta.workspace.sso_only is 'Developers of this workspace must sign in through an identity provider or directory (131).';
|
|
66
|
+
|
|
67
|
+
alter table meta.sso_pending alter column app_id drop not null;
|
|
68
|
+
alter table meta.sso_pending add column link_username text references meta.developer (username) on update cascade on delete cascade;
|
|
69
|
+
comment on column meta.sso_pending.app_id is 'The application signed in to; null: the builder (131).';
|
|
@@ -221,7 +221,7 @@ the 26.2 comparison.)
|
|
|
221
221
|
|
|
222
222
|
| APEX | pgkiln | Notes |
|
|
223
223
|
|---|---|---|
|
|
224
|
-
| Developer accounts, workspace sign-in | ✅ | (sprint 61) The builder's sign-in page like APEX's: workspace (optional; it becomes the current one, a wrong one answers like a wrong password), username, password with a Show button, *Remember workspace and username*, a link to Administration. (sprint 68) **Find my workspace**: developers' e-mail addresses, and a mail with their workspaces through the builder's mail service (the same answer for every address, limited per address and IP). (sprint 121) The sign-in page's language links: Deutsch, English, Español, Français, Nederlands, Português (WS-7) |
|
|
224
|
+
| Developer accounts, workspace sign-in | ✅ | (sprint 61) The builder's sign-in page like APEX's: workspace (optional; it becomes the current one, a wrong one answers like a wrong password), username, password with a Show button, *Remember workspace and username*, a link to Administration. (sprint 68) **Find my workspace**: developers' e-mail addresses, and a mail with their workspaces through the builder's mail service (the same answer for every address, limited per address and IP). (sprint 121) The sign-in page's language links: Deutsch, English, Español, Français, Nederlands, Português (WS-7). (sprint 129, SEC-1) **Single sign-on for developers** (APEX: the workspace's authentication scheme): the applications' OpenID Connect and SAML providers and LDAP directories can sign developers in to the builder, linked by the provider's stable subject (or by the developer on Security), created from a developers group, with an administrators group; a workspace can require it of its developers, with break-glass administrators who keep the password and `pgkiln admin builder-sign-in password` ([chapter 3](guide/03-builder.md#single-sign-on-for-developers)) |
|
|
225
225
|
| Workspace home (tool cards, top apps and users, summary, workspace and system message) | ✅ | (sprint 109) `/builder/home` opens after sign-in ([chapter 3](guide/03-builder.md#workspace-home)): App Builder, SQL Workshop, Data Reporter and App Gallery cards; the workspace message edited in place; a Summary of applications, tables, developers and reporting applications; top applications, users and developers over an hour, a day or a week; a system message with the server's version and, with the optional release check (`PGKILN_RELEASE_CHECK`, off by default), *pgkiln X is available*. The App Builder moved to `/builder/apps`; `/builder` opens either, per workspace. |
|
|
226
226
|
| Workspace message, environment banner, workspace utilization, preferences and extension links (Manage Service) | ✅ | (sprint 61) An **Administration** menu in the icon rail; administrators and workspace administrators set a **workspace message** (on the App Builder home) and an **environment banner** (a coloured label on every builder page of the workspace), and see **workspace utilization** (applications, pages, page views, users, static files, schema storage) ([chapter 3](guide/03-builder.md#workspace-message-and-environment-banner)). (sprint 62) **Workspace preferences**: the SQL Workshop and the applications' RESTful services (REST modules) on or off per workspace; **extension links** in an Extensions menu of the rail ([chapter 3](guide/03-builder.md#workspace-preferences)). **Missing:** the account login controls of APEX's preferences (pgkiln's password policy and sign-in throttling are installation-wide) |
|
|
227
227
|
| Instance administration, install/upgrade logs (26.1) | ✅ | Versioned migrations (`npm run db:migrate`), every run logged; Workspace utilities → **Installation** (administrators): server version, install and upgrade runs with errors, applied migrations, and warnings for migrations the database misses or the server doesn't know ([chapter 3](guide/03-builder.md)). Workspace utilities → **Instance settings**: session length and sign-in throttling (stored in the database, over the environment variables) and an overview of the server's configuration without secrets; a server whose database lacks migrations says so (503) or applies them (`MIGRATE_ON_START`). Other settings stay environment variables |
|
package/docs/guide/03-builder.md
CHANGED
|
@@ -111,6 +111,51 @@ setting the days to 0 stops every trusted browser at once.
|
|
|
111
111
|
`2fa_enrolled`, `2fa_recovery_used`), and resets and policy changes are mailed to the administrators when the
|
|
112
112
|
builder has a mail service (*Builder mail*).
|
|
113
113
|
|
|
114
|
+
### Single sign-on for developers
|
|
115
|
+
|
|
116
|
+
(0.63) Developers can sign in to the builder through the organisation's **identity provider** (OpenID Connect or SAML
|
|
117
|
+
2.0) or an **LDAP directory**, the ones applications use (Workspace utilities → Users → *Identity providers*,
|
|
118
|
+
*LDAP directories*; [chapter 8](08-security.md)). APEX does this with the workspace's authentication scheme.
|
|
119
|
+
|
|
120
|
+
**Turning it on.** On the provider's or directory's page, administrators fill in **Builder sign-in**:
|
|
121
|
+
|
|
122
|
+
- *Developers can sign in to the builder with it*: the sign-in page shows **Sign in with Company IdP** under the
|
|
123
|
+
password form. A directory adds a **Sign in with** choice above the user name (*pgkiln account* or the directory),
|
|
124
|
+
and the password goes to the directory. The provider's redirect URI (or SAML ACS URL) is the one registered for
|
|
125
|
+
applications already.
|
|
126
|
+
- *Create developers on their first sign-in*, for members of the **Developers group** (anyone, when it is empty).
|
|
127
|
+
They start as developers in the Default workspace, with the provider's e-mail address and a password nobody knows.
|
|
128
|
+
- **Administrators group**: when set, its members are instance administrators and nobody else is, checked at every
|
|
129
|
+
sign-in (break-glass administrators, below, are never demoted).
|
|
130
|
+
- *Link an existing developer with the same user name*: once, at the first sign-in, never a break-glass
|
|
131
|
+
administrator, and an e-mail address as user name only when the provider says it is verified. Leave it off unless
|
|
132
|
+
you trust the provider's user names; developers can link themselves instead.
|
|
133
|
+
|
|
134
|
+
**Linking.** A developer is found by the provider's stable subject (`sub`, the SAML NameID, the entry's `entryUUID`),
|
|
135
|
+
never by a name the provider sends. A developer who signs in with the password links an identity on **Security** →
|
|
136
|
+
*Single sign-on* → **Link Company IdP**: the provider's page opens and the identity is linked to the developer who
|
|
137
|
+
started it (in the same browser). The region lists linked identities, with **Unlink**. A provider's page lists the
|
|
138
|
+
developers linked to it; administrators unlink any of them.
|
|
139
|
+
|
|
140
|
+
Signing in through a provider skips pgkiln's own second step and the enrolment on Security: the provider decides on
|
|
141
|
+
the second factor. A directory checks a password, so pgkiln's second step follows as with the pgkiln password.
|
|
142
|
+
|
|
143
|
+
**Requiring it per workspace.** A workspace's page (*Preferences* → *Single sign-on*) has *This workspace's
|
|
144
|
+
developers must sign in through an identity provider* (or a directory). Its developers' pgkiln password and a passkey
|
|
145
|
+
alone are refused from then on; the page lists members without a linked identity, who can't sign in until they link
|
|
146
|
+
one. It can be turned on only while there is a **break-glass administrator**, and only by someone who has a linked
|
|
147
|
+
identity or is break-glass, so nobody locks themselves out.
|
|
148
|
+
|
|
149
|
+
**Break-glass administrators.** On **Developers**, an administrator makes another administrator *break-glass*: they
|
|
150
|
+
may still sign in with the pgkiln password where a workspace requires single sign-on, for when the identity provider
|
|
151
|
+
is down or misconfigured. Give them a second factor and a long password, and keep the password somewhere safe. The
|
|
152
|
+
last break-glass administrator can't lose it while a workspace requires single sign-on, nor be made a developer or
|
|
153
|
+
removed. If nobody can sign in at all: `pgkiln admin builder-sign-in password` on the server turns the workspaces'
|
|
154
|
+
rule off ([chapter 18](18-cli.md)).
|
|
155
|
+
|
|
156
|
+
Sign-ins are in the activity log as `login` with *builder sso:company* or *builder ldap:corp*; failures as
|
|
157
|
+
`login_failed` with the reason; linking, unlinking and settings as `sso_linked`, `sso_unlinked` and `sso_policy`.
|
|
158
|
+
|
|
114
159
|
### Locked out?
|
|
115
160
|
|
|
116
161
|
| Situation | What to do |
|
|
@@ -120,6 +165,7 @@ setting the days to 0 stops every trusted browser at once.
|
|
|
120
165
|
| A passkey no longer works after a move to another domain | Sign in with an app code or a recovery code and add a new passkey ([above](#two-factor-sign-in)) |
|
|
121
166
|
| No administrator can sign in | On the server: `pgkiln admin reset-2fa <developer> --reason "…"` ([chapter 18](18-cli.md)), then sign in with the password |
|
|
122
167
|
| The policy locks everyone out | On the server: `pgkiln admin set-2fa-policy off` (also clears the workspaces' own rules) |
|
|
168
|
+
| The identity provider is down and a workspace requires single sign-on | A break-glass administrator signs in with the password and turns the workspace's rule off; without one, on the server: `pgkiln admin builder-sign-in password` |
|
|
123
169
|
| Codes are always wrong | Check the time on the phone and on the server (codes allow 30 seconds either way) |
|
|
124
170
|
| The instance key was lost or changed | The apps' secrets can't be read: reset the developers (`pgkiln admin reset-2fa`); recovery codes still work |
|
|
125
171
|
|
|
@@ -148,7 +148,8 @@ select id, 'alice', meta.hash_password('a-strong-password'), '{manager}' from me
|
|
|
148
148
|
### Single sign-on (OpenID Connect)
|
|
149
149
|
|
|
150
150
|
Applications can let people sign in with your organisation's identity provider: Microsoft Entra
|
|
151
|
-
ID, Google Workspace, Okta, Keycloak, Auth0 or any other OpenID Connect provider.
|
|
151
|
+
ID, Google Workspace, Okta, Keycloak, Auth0 or any other OpenID Connect provider. (0.63) The same providers, SAML
|
|
152
|
+
ones and LDAP directories can sign developers in to the builder too ([chapter 3](03-builder.md#single-sign-on-for-developers)).
|
|
152
153
|
|
|
153
154
|
**1. Register pgkiln at the provider** as a *web application* (confidential client) using the
|
|
154
155
|
authorization code flow. The redirect URI is `<PUBLIC_URL>/sso/callback/<name>`, for example
|
|
@@ -56,6 +56,8 @@ src/
|
|
|
56
56
|
sso.ts OpenID Connect: discovery, sign-in flow, ID token checks, account linking
|
|
57
57
|
saml.ts SAML 2.0 sign-in (node-saml): AuthnRequest, response checks, SP metadata
|
|
58
58
|
ldap.ts LDAP directories: search + bind, groups, account linking (ldapts)
|
|
59
|
+
devsso.ts (sprint 129) developers through identity providers and directories: resolveDeveloper (subject, link, create,
|
|
60
|
+
administrators group), linkDeveloper, ssoRequired (a workspace's rule, break-glass), clearSsoOnly
|
|
59
61
|
headerauth.ts HTTP-header authentication: trusted proxies (PGKILN_AUTH_HEADER_PROXIES), header checks, accounts
|
|
60
62
|
dbauth.ts database-account authentication: role lists, a short connection as the role (DATABASE_URL target), membership/superuser checks
|
|
61
63
|
customauth.ts custom authentication: the app's function or PL/pgSQL body (a pg_temp function) and post-authentication code, as the app's role
|
|
@@ -343,6 +345,7 @@ src/
|
|
|
343
345
|
quicksql.ts SQL Workshop → Quick SQL page (preview, save as script, run)
|
|
344
346
|
querybuilder.ts SQL Workshop → Query Builder: catalog, joins by foreign key or drawn, functions/group by, buildQuery() from the URL; the canvas is in builder.js
|
|
345
347
|
users.ts user directory and identity providers
|
|
348
|
+
builder-sso.ts (sprint 129) a provider's or directory's Builder sign-in region, Security's identities, unlink, break-glass
|
|
346
349
|
datapump.ts (sprint 103, DA-16) SQL Workshop → Schema Export / Import (/builder/sql/data-pump, administrators only):
|
|
347
350
|
jobs, export form, upload (streamed to PGKILN_DUMP_DIR), archive preview and import options, job log, download
|
|
348
351
|
db-users.ts (sprint 99, DA-13) Administration → Database Users (/builder/db-users, administrators only): PostgreSQL
|
package/docs/guide/18-cli.md
CHANGED
|
@@ -54,6 +54,7 @@ any (see [what is not exported](03-builder.md#export-format)).
|
|
|
54
54
|
| `pgkiln users password <username> [--developer]` | Sets a password and ends that user's sessions |
|
|
55
55
|
| `pgkiln admin reset-2fa <developer> [--reason <text>]` | (0.60) Removes a developer's authenticator apps and recovery codes and ends their builder sessions: [locked out?](03-builder.md#locked-out) |
|
|
56
56
|
| `pgkiln admin set-2fa-policy off\|admins\|all [--grace <days>]` | (0.60) Sets the builder's [two-factor policy](03-builder.md#two-factor-sign-in); `off` always works (and, 0.62, clears every workspace's own rule), the others need two administrators with an app |
|
|
57
|
+
| `pgkiln admin builder-sign-in password` | (0.63) Turns off every workspace's [single sign-on rule](03-builder.md#single-sign-on-for-developers), so developers may sign in with the pgkiln password again; logged and mailed to the administrators |
|
|
57
58
|
|
|
58
59
|
`users` reads the password from standard input (the first line, e.g. from a secret store:
|
|
59
60
|
`printf '%s\n' "$PW" | pgkiln users add ops --developer`) or asks for it twice on a terminal. It is
|
package/package.json
CHANGED
package/public/builder.css
CHANGED
|
@@ -676,6 +676,10 @@ kbd { display: inline-block; padding: 0 0.35em; border: 1px solid var(--border);
|
|
|
676
676
|
.signin-remember { font-size: 0.9rem; }
|
|
677
677
|
.signin-submit { padding: 0.75rem; font-size: 1rem; border-radius: var(--radius); }
|
|
678
678
|
[data-passkey-status]:empty { display: none; }
|
|
679
|
+
/* (sprint 129) linked identities, builder sign-in */
|
|
680
|
+
.plain-list { list-style: none; padding: 0; margin: .25rem 0 .75rem; }
|
|
681
|
+
.plain-list li { padding: .2rem 0; }
|
|
682
|
+
.signin-idp { display: flex; align-items: center; justify-content: center; gap: .5rem; text-decoration: none; }
|
|
679
683
|
.signin-hint { margin: 0; text-align: center; }
|
|
680
684
|
.signin-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem 1.5rem; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border); }
|
|
681
685
|
.signin-actions a { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--gold); text-decoration: none; }
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import { owner } from '../db.ts';
|
|
3
|
+
import { html, raw, type Raw } from '../html.ts';
|
|
4
|
+
import { icon } from '../icons.ts';
|
|
5
|
+
import { clientIp, logActivity, type Session } from '../session.ts';
|
|
6
|
+
import { breakGlassAdmins, builderSources, identitiesOf, ssoRequired } from '../devsso.ts';
|
|
7
|
+
import { isAdmin } from './locks.ts';
|
|
8
|
+
import { tr } from './i18n.ts';
|
|
9
|
+
import { administrator, back, BASE, csrf, developer, flash, input, region, type Req } from './ui.ts';
|
|
10
|
+
|
|
11
|
+
// (sprint 129, SEC-1) Builder sign-in through identity providers and LDAP directories (devsso.ts):
|
|
12
|
+
// * Users → Identity providers / LDAP directories → "Builder sign-in": on or off, creating developers,
|
|
13
|
+
// linking existing ones, the groups; the developers linked there;
|
|
14
|
+
// * Security: the developer's own identities, "Link" for each provider, "Unlink";
|
|
15
|
+
// * Developers: break-glass administrators (they may use the pgkiln password where a workspace
|
|
16
|
+
// requires single sign-on).
|
|
17
|
+
|
|
18
|
+
type Kind = 'provider' | 'directory';
|
|
19
|
+
const TABLE = { provider: 'meta.auth_provider', directory: 'meta.ldap_directory' } as const;
|
|
20
|
+
const COLUMN = { provider: 'provider_id', directory: 'directory_id' } as const;
|
|
21
|
+
const PATH = { provider: 'providers', directory: 'directories' } as const;
|
|
22
|
+
|
|
23
|
+
const check = (name: string, label: string, on: boolean, help?: string) =>
|
|
24
|
+
html`<div class="field"><label class="check"><input type="checkbox" name="${name}" value="true"${on ? raw(' checked') : ''}> ${label}</label>${help ? html`<small class="help">${help}</small>` : ''}</div>`;
|
|
25
|
+
|
|
26
|
+
/** The "Builder sign-in" region of a provider's or directory's page. */
|
|
27
|
+
export async function builderSignInRegion(s: Session, kind: Kind, row: { id: number; builder: boolean; builder_auto_create: boolean; builder_link_existing: boolean; builder_group: string | null; builder_admin_group: string | null }): Promise<Raw> {
|
|
28
|
+
const tx = tr(s);
|
|
29
|
+
const linked = (await owner.query<{ id: number; username: string; linked_at: string }>(
|
|
30
|
+
`select id, username, linked_at from meta.developer_identity where ${COLUMN[kind]} = $1 order by username`, [row.id])).rows;
|
|
31
|
+
return region(tx('bsso.title'), html`<p class="muted u-mt0">${tx('bsso.intro')}</p>
|
|
32
|
+
<form method="post" action="${BASE}/users/${PATH[kind]}/${row.id}/builder">${csrf(s)}
|
|
33
|
+
${check('builder', tx('bsso.on'), row.builder, tx('bsso.on_help'))}
|
|
34
|
+
${check('builder_auto_create', tx('bsso.auto_create'), row.builder_auto_create, tx('bsso.auto_create_help'))}
|
|
35
|
+
${check('builder_link_existing', tx('bsso.link_existing'), row.builder_link_existing, tx('bsso.link_existing_help'))}
|
|
36
|
+
<div class="form-grid">
|
|
37
|
+
${input('builder_group', tx('bsso.group'), row.builder_group ?? '', { help: tx('bsso.group_help') })}
|
|
38
|
+
${input('builder_admin_group', tx('bsso.admin_group'), row.builder_admin_group ?? '', { help: tx('bsso.admin_group_help') })}
|
|
39
|
+
</div>
|
|
40
|
+
<div class="buttons"><button class="btn">${tx('bsso.save')}</button></div></form>
|
|
41
|
+
<h3>${tx('bsso.linked')}</h3>
|
|
42
|
+
${linked.length
|
|
43
|
+
? html`<ul class="plain-list">${linked.map((l) => html`<li>${icon('user')} ${l.username} <span class="muted small">${String(l.linked_at).slice(0, 10)}</span>
|
|
44
|
+
<form method="post" action="${BASE}/developers/identity/${l.id}/unlink" class="u-inline">${csrf(s)}<button class="link-button" data-confirm="${tx('bsso.unlink_confirm', { user: l.username })}">${tx('bsso.unlink')}</button></form></li>`)}</ul>`
|
|
45
|
+
: html`<p class="muted">${tx('bsso.none_linked')}</p>`}`, '', 'builder-sign-in');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The Security page's region: the developer's identities, and the providers to link. */
|
|
49
|
+
export async function identitiesRegion(s: Session, linkedNow: string | undefined): Promise<Raw | ''> {
|
|
50
|
+
const tx = tr(s);
|
|
51
|
+
const [mine, sources] = await Promise.all([identitiesOf(s.username!), builderSources()]);
|
|
52
|
+
if (!mine.length && !sources.providers.length && !sources.directories.length) return '';
|
|
53
|
+
const linkable = sources.providers.filter((p) => !mine.some((m) => m.kind === 'provider' && m.source === p.name));
|
|
54
|
+
const linked = linkedNow ? mine.find((m) => m.kind === 'provider' && m.source === linkedNow) : undefined;
|
|
55
|
+
return region(tx('bsso.yours'), html`<p class="muted u-mt0">${tx('bsso.yours_intro')}</p>
|
|
56
|
+
${linked ? html`<div class="alert alert-success" role="status">${tx('bsso.linked_now', { provider: linked.display_name })}</div>` : ''}
|
|
57
|
+
${mine.length
|
|
58
|
+
? html`<ul class="plain-list">${mine.map((m) => html`<li>${icon(m.kind === 'provider' ? 'log-in' : 'server')} ${m.display_name} <span class="muted small">${String(m.linked_at).slice(0, 10)}</span>
|
|
59
|
+
<form method="post" action="${BASE}/developers/identity/${m.id}/unlink" class="u-inline">${csrf(s)}<button class="link-button" data-confirm="${tx('bsso.unlink_mine_confirm', { provider: m.display_name })}">${tx('bsso.unlink')}</button></form></li>`)}</ul>`
|
|
60
|
+
: html`<p class="muted">${tx('bsso.none_yours')}</p>`}
|
|
61
|
+
${linkable.map((p) => html`<form method="post" action="${BASE}/security/link/${p.name}" class="u-inline">${csrf(s)}<button class="btn">${icon('link')} ${tx('bsso.link', { provider: p.display_name })}</button></form> `)}
|
|
62
|
+
${sources.directories.length ? html`<p class="muted small">${tx('bsso.directories_note')}</p>` : ''}`, '', 'identities');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Would this change leave a workspace that requires single sign-on without a break-glass administrator? */
|
|
66
|
+
export async function leavesNoBreakGlass(username: string) {
|
|
67
|
+
if (!(await owner.one('select 1 as ok from meta.workspace where sso_only limit 1'))) return false;
|
|
68
|
+
const left = (await breakGlassAdmins()).filter((u) => u !== username);
|
|
69
|
+
return left.length === 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function builderSsoRoutes(app: FastifyInstance) {
|
|
73
|
+
for (const kind of ['provider', 'directory'] as Kind[])
|
|
74
|
+
app.post(`${BASE}/users/${PATH[kind]}/:id(^\\d{1,9}$)/builder`, async (req: Req, reply) => {
|
|
75
|
+
const s = await administrator(req, reply, kind === 'provider' ? 'Identity providers' : 'LDAP directories');
|
|
76
|
+
if (!s) return;
|
|
77
|
+
const b = req.body ?? {};
|
|
78
|
+
const group = (v: unknown) => String(v ?? '').trim().slice(0, 200) || null;
|
|
79
|
+
const on = b.builder === 'true';
|
|
80
|
+
const r = await owner.one<{ name: string }>(
|
|
81
|
+
`update ${TABLE[kind]} set builder = $2, builder_auto_create = $3, builder_link_existing = $4, builder_group = $5, builder_admin_group = $6
|
|
82
|
+
where id = $1 returning name`,
|
|
83
|
+
[req.params.id, on, b.builder_auto_create === 'true', b.builder_link_existing === 'true', group(b.builder_group), group(b.builder_admin_group)],
|
|
84
|
+
);
|
|
85
|
+
if (!r) return reply.code(404).send('Not found');
|
|
86
|
+
await logActivity({ username: s.username, event: 'sso_policy', ip: clientIp(req), detail: `${kind} ${r.name}: builder sign-in ${on ? 'on' : 'off'}` });
|
|
87
|
+
flash(s, tr(s)('bsso.saved'));
|
|
88
|
+
return back(reply, s, `${BASE}/users/${PATH[kind]}/${req.params.id}#builder-sign-in`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// unlinking: the developer's own identity, or any for an administrator
|
|
92
|
+
app.post(`${BASE}/developers/identity/:id(^\\d{1,9}$)/unlink`, async (req: Req, reply) => {
|
|
93
|
+
const s = await developer(req, reply);
|
|
94
|
+
if (!s) return;
|
|
95
|
+
const tx = tr(s);
|
|
96
|
+
const i = await owner.one<{ username: string; provider_id: number | null; directory_id: number | null }>('select username, provider_id, directory_id from meta.developer_identity where id = $1', [req.params.id]);
|
|
97
|
+
const admin = await isAdmin(s.username);
|
|
98
|
+
if (!i || (i.username !== s.username && !admin)) return reply.code(404).send('Not found');
|
|
99
|
+
const where = i.username === s.username ? `${BASE}/security#identities` : `${BASE}/users/${i.provider_id ? 'providers' : 'directories'}/${i.provider_id ?? i.directory_id}#builder-sign-in`;
|
|
100
|
+
// the last identity of a developer who must use single sign-on: they could not sign in any more
|
|
101
|
+
const only = await owner.one<{ n: number }>('select count(*)::int as n from meta.developer_identity where username = $1', [i.username]);
|
|
102
|
+
if (only!.n <= 1 && (await ssoRequired(i.username))) {
|
|
103
|
+
flash(s, tx('bsso.keep_one'), 'error');
|
|
104
|
+
return back(reply, s, where);
|
|
105
|
+
}
|
|
106
|
+
await owner.query('delete from meta.developer_identity where id = $1', [req.params.id]);
|
|
107
|
+
await logActivity({ username: s.username, event: 'sso_unlinked', ip: clientIp(req), detail: `${i.username}: ${i.provider_id ? `provider ${i.provider_id}` : `directory ${i.directory_id}`}` });
|
|
108
|
+
flash(s, tx('bsso.unlinked'));
|
|
109
|
+
return back(reply, s, where);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// break-glass: administrators only, for administrators
|
|
113
|
+
app.post(`${BASE}/developers/break-glass`, async (req: Req, reply) => {
|
|
114
|
+
const s = await developer(req, reply);
|
|
115
|
+
if (!s) return;
|
|
116
|
+
if (!(await isAdmin(s.username))) return reply.code(403).send('Only administrators change break-glass access.');
|
|
117
|
+
const tx = tr(s);
|
|
118
|
+
const username = String(req.body?.username ?? '');
|
|
119
|
+
const on = req.body?.break_glass === 'true';
|
|
120
|
+
const d = await owner.one<{ is_admin: boolean }>('select is_admin from meta.developer where username = $1', [username]);
|
|
121
|
+
if (!d) return reply.code(404).send('Not found');
|
|
122
|
+
if (on && !d.is_admin) {
|
|
123
|
+
flash(s, tx('bsso.break_glass_admins'), 'error');
|
|
124
|
+
return back(reply, s, `${BASE}/developers`);
|
|
125
|
+
}
|
|
126
|
+
if (!on && (await leavesNoBreakGlass(username))) {
|
|
127
|
+
flash(s, tx('bsso.keep_break_glass'), 'error');
|
|
128
|
+
return back(reply, s, `${BASE}/developers`);
|
|
129
|
+
}
|
|
130
|
+
await owner.query('update meta.developer set break_glass = $2 where username = $1', [username, on]);
|
|
131
|
+
await logActivity({ username: s.username, event: 'sso_policy', ip: clientIp(req), detail: `${username}: break-glass ${on ? 'on' : 'off'}` });
|
|
132
|
+
flash(s, tx(on ? 'bsso.break_glass_on' : 'bsso.break_glass_off', { user: username }));
|
|
133
|
+
return back(reply, s, `${BASE}/developers`);
|
|
134
|
+
});
|
|
135
|
+
}
|
package/src/builder/i18n/de.ts
CHANGED
|
@@ -1820,4 +1820,54 @@ export const de: BuilderMessages = {
|
|
|
1820
1820
|
'wsp.mfa_save': 'Zwei-Faktor-Regel speichern',
|
|
1821
1821
|
'wsp.mfa_on': 'Die Entwickler dieses Workspace brauchen die Zwei-Faktor-Anmeldung.',
|
|
1822
1822
|
'wsp.mfa_off': 'Der Workspace verlangt keine Zwei-Faktor-Anmeldung mehr (die Richtlinie der Instanz gilt weiter).',
|
|
1823
|
+
'signin.method': 'Anmelden mit',
|
|
1824
|
+
'signin.method_pgkiln': 'pgkiln-Konto',
|
|
1825
|
+
'signin.with': 'Mit {provider} anmelden',
|
|
1826
|
+
'signin.ldap_unavailable': '{directory} ist nicht erreichbar. Versuchen Sie es später erneut.',
|
|
1827
|
+
'signin.sso_required': 'Ihr Workspace verlangt Single Sign-on: Melden Sie sich über Ihren Identitätsanbieter an.',
|
|
1828
|
+
'signin.sso_unavailable': '{provider} ist nicht erreichbar. Versuchen Sie es später erneut.',
|
|
1829
|
+
'bsso.title': 'Builder-Anmeldung',
|
|
1830
|
+
'bsso.intro': 'Entwickler können sich auch mit diesem Anbieter im Builder anmelden. Sie werden über die verknüpfte Identität gefunden, nie nur über den Namen.',
|
|
1831
|
+
'bsso.on': 'Entwickler können sich damit im Builder anmelden',
|
|
1832
|
+
'bsso.on_help': 'Die Anmeldeseite zeigt ihn an. Bei dieser Anmeldung entfällt der zweite Faktor von pgkiln: Der Anbieter entscheidet.',
|
|
1833
|
+
'bsso.auto_create': 'Entwickler bei der ersten Anmeldung anlegen',
|
|
1834
|
+
'bsso.auto_create_help': 'Für Mitglieder der unten angegebenen Entwicklergruppe (alle, wenn sie leer ist). Neue Entwickler kommen in den Default-Workspace.',
|
|
1835
|
+
'bsso.link_existing': 'Vorhandenen Entwickler mit demselben Benutzernamen verknüpfen',
|
|
1836
|
+
'bsso.link_existing_help': 'Einmalig, bei der ersten Anmeldung; nie ein Notfall-Administrator. Nur einschalten, wenn Sie den Benutzernamen des Anbieters vertrauen. Sonst verknüpfen Entwickler die Identität auf ihrer Sicherheitsseite.',
|
|
1837
|
+
'bsso.group': 'Entwicklergruppe',
|
|
1838
|
+
'bsso.group_help': 'Nur ihre Mitglieder erhalten ein Entwicklerkonto. Leer: alle, die der Anbieter anmeldet.',
|
|
1839
|
+
'bsso.admin_group': 'Administratorengruppe',
|
|
1840
|
+
'bsso.admin_group_help': 'Wenn gesetzt, sind ihre Mitglieder Administratoren der Instanz und andere nicht, geprüft bei jeder Anmeldung (Notfall-Administratoren behalten ihre Rolle).',
|
|
1841
|
+
'bsso.save': 'Builder-Anmeldung speichern',
|
|
1842
|
+
'bsso.saved': 'Builder-Anmeldung gespeichert.',
|
|
1843
|
+
'bsso.linked': 'Verknüpfte Entwickler',
|
|
1844
|
+
'bsso.none_linked': 'Noch kein Entwickler verknüpft.',
|
|
1845
|
+
'bsso.unlink': 'Verknüpfung lösen',
|
|
1846
|
+
'bsso.unlink_confirm': '{user} lösen? Die Person meldet sich mit dem Kennwort an oder verknüpft erneut.',
|
|
1847
|
+
'bsso.unlink_mine_confirm': '{provider} lösen? Sie können erneut verknüpfen.',
|
|
1848
|
+
'bsso.unlinked': 'Die Identität ist nicht mehr verknüpft.',
|
|
1849
|
+
'bsso.keep_one': 'Ein Workspace verlangt von diesem Entwickler Single Sign-on: Behalten Sie eine verknüpfte Identität.',
|
|
1850
|
+
'bsso.yours': 'Single Sign-on',
|
|
1851
|
+
'bsso.yours_intro': 'Die Identitätsanbieter, mit denen Sie sich im Builder anmelden können.',
|
|
1852
|
+
'bsso.none_yours': 'Noch keine Identität verknüpft.',
|
|
1853
|
+
'bsso.link': '{provider} verknüpfen',
|
|
1854
|
+
'bsso.linked_now': '{provider} ist verknüpft: Sie können sich damit anmelden.',
|
|
1855
|
+
'bsso.directories_note': 'Ein LDAP-Verzeichnis wird bei der ersten Anmeldung damit verknüpft (ein Administrator erlaubt das pro Verzeichnis).',
|
|
1856
|
+
'bsso.break_glass': 'Notfallzugang',
|
|
1857
|
+
'bsso.break_glass_help': 'Darf sich mit dem pgkiln-Kennwort anmelden, wo ein Workspace Single Sign-on verlangt – für den Fall, dass der Identitätsanbieter ausfällt.',
|
|
1858
|
+
'bsso.break_glass_make': 'Notfallzugang geben',
|
|
1859
|
+
'bsso.break_glass_remove': 'Notfallzugang entziehen',
|
|
1860
|
+
'bsso.break_glass_on': '{user} ist Notfall-Administrator. Richten Sie auch einen zweiten Faktor ein.',
|
|
1861
|
+
'bsso.break_glass_off': '{user} ist kein Notfall-Administrator mehr.',
|
|
1862
|
+
'bsso.break_glass_admins': 'Nur Administratoren können Notfallzugang haben.',
|
|
1863
|
+
'bsso.keep_break_glass': 'Ein Workspace verlangt Single Sign-on: Behalten Sie mindestens einen Notfall-Administrator.',
|
|
1864
|
+
'wsp.sso': 'Single Sign-on',
|
|
1865
|
+
'wsp.sso_only': 'Die Entwickler dieses Workspace müssen sich über einen Identitätsanbieter anmelden',
|
|
1866
|
+
'wsp.sso_only_help': 'Oder ein LDAP-Verzeichnis. Das pgkiln-Kennwort und ein Passkey allein werden abgelehnt, außer für Notfall-Administratoren. `pgkiln admin builder-sign-in password` schaltet es ab.',
|
|
1867
|
+
'wsp.sso_unlinked': 'Noch keine verknüpfte Identität (können sich nicht anmelden): {list}.',
|
|
1868
|
+
'wsp.sso_save': 'Single-Sign-on-Regel speichern',
|
|
1869
|
+
'wsp.sso_on': 'Die Entwickler dieses Workspace müssen Single Sign-on verwenden.',
|
|
1870
|
+
'wsp.sso_off': 'Die Entwickler des Workspace dürfen wieder das pgkiln-Kennwort verwenden.',
|
|
1871
|
+
'wsp.sso_no_break_glass': 'Geben Sie zuerst einem Administrator Notfallzugang (Entwickler), damit sich jemand anmelden kann, wenn der Identitätsanbieter ausfällt.',
|
|
1872
|
+
'wsp.sso_link_first': 'Verknüpfen Sie zuerst auf Ihrer Sicherheitsseite eine Identität, sonst könnten Sie sich selbst nicht anmelden.',
|
|
1823
1873
|
};
|
package/src/builder/i18n/en.ts
CHANGED
|
@@ -1823,4 +1823,54 @@ export const en = {
|
|
|
1823
1823
|
'wsp.mfa_save': 'Save two-factor rule',
|
|
1824
1824
|
'wsp.mfa_on': 'This workspace’s developers need two-factor sign-in.',
|
|
1825
1825
|
'wsp.mfa_off': 'The workspace no longer requires two-factor sign-in (the instance’s policy still applies).',
|
|
1826
|
+
'signin.method': 'Sign in with',
|
|
1827
|
+
'signin.method_pgkiln': 'pgkiln account',
|
|
1828
|
+
'signin.with': 'Sign in with {provider}',
|
|
1829
|
+
'signin.ldap_unavailable': '{directory} cannot be reached. Try again later.',
|
|
1830
|
+
'signin.sso_required': 'Your workspace requires single sign-on: use your identity provider.',
|
|
1831
|
+
'signin.sso_unavailable': '{provider} cannot be reached. Try again later.',
|
|
1832
|
+
'bsso.title': 'Builder sign-in',
|
|
1833
|
+
'bsso.intro': 'Developers can sign in to the builder with this provider too. They are found by the identity they linked, never by name alone.',
|
|
1834
|
+
'bsso.on': 'Developers can sign in to the builder with it',
|
|
1835
|
+
'bsso.on_help': 'The sign-in page shows it. Signing in this way skips pgkiln’s own second factor: the provider decides.',
|
|
1836
|
+
'bsso.auto_create': 'Create developers on their first sign-in',
|
|
1837
|
+
'bsso.auto_create_help': 'For members of the developers group below (anyone, when it is empty). New developers join the Default workspace.',
|
|
1838
|
+
'bsso.link_existing': 'Link an existing developer with the same user name',
|
|
1839
|
+
'bsso.link_existing_help': 'Once, on the first sign-in; never a break-glass administrator. Turn it on only when the provider’s user names are yours to trust. Otherwise developers link the identity on their Security page.',
|
|
1840
|
+
'bsso.group': 'Developers group',
|
|
1841
|
+
'bsso.group_help': 'Only its members get a developer account. Empty: anyone the provider signs in.',
|
|
1842
|
+
'bsso.admin_group': 'Administrators group',
|
|
1843
|
+
'bsso.admin_group_help': 'When set, its members are instance administrators and others are not, checked at every sign-in (break-glass administrators keep their role).',
|
|
1844
|
+
'bsso.save': 'Save builder sign-in',
|
|
1845
|
+
'bsso.saved': 'Builder sign-in saved.',
|
|
1846
|
+
'bsso.linked': 'Linked developers',
|
|
1847
|
+
'bsso.none_linked': 'No developer is linked yet.',
|
|
1848
|
+
'bsso.unlink': 'Unlink',
|
|
1849
|
+
'bsso.unlink_confirm': 'Unlink {user}? They sign in with the password, or link again.',
|
|
1850
|
+
'bsso.unlink_mine_confirm': 'Unlink {provider}? You can link it again.',
|
|
1851
|
+
'bsso.unlinked': 'The identity is unlinked.',
|
|
1852
|
+
'bsso.keep_one': 'A workspace requires single sign-on of this developer: keep one linked identity.',
|
|
1853
|
+
'bsso.yours': 'Single sign-on',
|
|
1854
|
+
'bsso.yours_intro': 'The identity providers you can sign in to the builder with.',
|
|
1855
|
+
'bsso.none_yours': 'No identity linked yet.',
|
|
1856
|
+
'bsso.link': 'Link {provider}',
|
|
1857
|
+
'bsso.linked_now': '{provider} is linked: you can sign in with it.',
|
|
1858
|
+
'bsso.directories_note': 'An LDAP directory is linked when you first sign in with it (an administrator allows that per directory).',
|
|
1859
|
+
'bsso.break_glass': 'break-glass',
|
|
1860
|
+
'bsso.break_glass_help': 'May sign in with the pgkiln password where a workspace requires single sign-on, for when the identity provider fails.',
|
|
1861
|
+
'bsso.break_glass_make': 'Make break-glass',
|
|
1862
|
+
'bsso.break_glass_remove': 'Remove break-glass',
|
|
1863
|
+
'bsso.break_glass_on': '{user} is a break-glass administrator. Give them a second factor too.',
|
|
1864
|
+
'bsso.break_glass_off': '{user} is no longer a break-glass administrator.',
|
|
1865
|
+
'bsso.break_glass_admins': 'Only administrators can have break-glass access.',
|
|
1866
|
+
'bsso.keep_break_glass': 'A workspace requires single sign-on: keep at least one break-glass administrator.',
|
|
1867
|
+
'wsp.sso': 'Single sign-on',
|
|
1868
|
+
'wsp.sso_only': 'This workspace’s developers must sign in through an identity provider',
|
|
1869
|
+
'wsp.sso_only_help': 'Or an LDAP directory. The pgkiln password and a passkey alone are refused, except for break-glass administrators. `pgkiln admin builder-sign-in password` turns it off.',
|
|
1870
|
+
'wsp.sso_unlinked': 'No linked identity yet (they cannot sign in): {list}.',
|
|
1871
|
+
'wsp.sso_save': 'Save single sign-on rule',
|
|
1872
|
+
'wsp.sso_on': 'This workspace’s developers must use single sign-on.',
|
|
1873
|
+
'wsp.sso_off': 'The workspace’s developers may use the pgkiln password again.',
|
|
1874
|
+
'wsp.sso_no_break_glass': 'Make an administrator break-glass first (Developers), so someone can still sign in when the identity provider fails.',
|
|
1875
|
+
'wsp.sso_link_first': 'Link an identity on your Security page first, or you could not sign in yourself.',
|
|
1826
1876
|
} as const;
|
package/src/builder/i18n/es.ts
CHANGED
|
@@ -1820,4 +1820,54 @@ export const es: BuilderMessages = {
|
|
|
1820
1820
|
'wsp.mfa_save': 'Guardar la regla de dos pasos',
|
|
1821
1821
|
'wsp.mfa_on': 'Los desarrolladores de este espacio de trabajo necesitan el inicio de sesión en dos pasos.',
|
|
1822
1822
|
'wsp.mfa_off': 'El espacio de trabajo ya no exige el inicio de sesión en dos pasos (la política de la instancia sigue vigente).',
|
|
1823
|
+
'signin.method': 'Iniciar sesión con',
|
|
1824
|
+
'signin.method_pgkiln': 'Cuenta de pgkiln',
|
|
1825
|
+
'signin.with': 'Iniciar sesión con {provider}',
|
|
1826
|
+
'signin.ldap_unavailable': 'No se puede acceder a {directory}. Inténtelo más tarde.',
|
|
1827
|
+
'signin.sso_required': 'Su espacio de trabajo exige el inicio de sesión único: use su proveedor de identidad.',
|
|
1828
|
+
'signin.sso_unavailable': 'No se puede acceder a {provider}. Inténtelo más tarde.',
|
|
1829
|
+
'bsso.title': 'Inicio de sesión en el builder',
|
|
1830
|
+
'bsso.intro': 'Los desarrolladores también pueden iniciar sesión en el builder con este proveedor. Se les identifica por la identidad vinculada, nunca solo por el nombre.',
|
|
1831
|
+
'bsso.on': 'Los desarrolladores pueden iniciar sesión en el builder con él',
|
|
1832
|
+
'bsso.on_help': 'La página de inicio de sesión lo muestra. Así se omite el segundo factor propio de pgkiln: decide el proveedor.',
|
|
1833
|
+
'bsso.auto_create': 'Crear desarrolladores en su primer inicio de sesión',
|
|
1834
|
+
'bsso.auto_create_help': 'Para los miembros del grupo de desarrolladores de abajo (cualquiera, si está vacío). Los nuevos desarrolladores entran en el espacio de trabajo Default.',
|
|
1835
|
+
'bsso.link_existing': 'Vincular un desarrollador existente con el mismo nombre de usuario',
|
|
1836
|
+
'bsso.link_existing_help': 'Una vez, en el primer inicio de sesión; nunca un administrador de emergencia. Actívelo solo si confía en los nombres de usuario del proveedor. Si no, los desarrolladores vinculan la identidad en su página de seguridad.',
|
|
1837
|
+
'bsso.group': 'Grupo de desarrolladores',
|
|
1838
|
+
'bsso.group_help': 'Solo sus miembros reciben una cuenta de desarrollador. Vacío: cualquiera que el proveedor autentique.',
|
|
1839
|
+
'bsso.admin_group': 'Grupo de administradores',
|
|
1840
|
+
'bsso.admin_group_help': 'Si se indica, sus miembros son administradores de la instancia y los demás no; se comprueba en cada inicio de sesión (los administradores de emergencia conservan su rol).',
|
|
1841
|
+
'bsso.save': 'Guardar el inicio de sesión en el builder',
|
|
1842
|
+
'bsso.saved': 'Inicio de sesión en el builder guardado.',
|
|
1843
|
+
'bsso.linked': 'Desarrolladores vinculados',
|
|
1844
|
+
'bsso.none_linked': 'Aún no hay ningún desarrollador vinculado.',
|
|
1845
|
+
'bsso.unlink': 'Desvincular',
|
|
1846
|
+
'bsso.unlink_confirm': '¿Desvincular a {user}? Iniciará sesión con la contraseña o volverá a vincular.',
|
|
1847
|
+
'bsso.unlink_mine_confirm': '¿Desvincular {provider}? Puede volver a vincularlo.',
|
|
1848
|
+
'bsso.unlinked': 'La identidad está desvinculada.',
|
|
1849
|
+
'bsso.keep_one': 'Un espacio de trabajo exige a este desarrollador el inicio de sesión único: mantenga una identidad vinculada.',
|
|
1850
|
+
'bsso.yours': 'Inicio de sesión único',
|
|
1851
|
+
'bsso.yours_intro': 'Los proveedores de identidad con los que puede iniciar sesión en el builder.',
|
|
1852
|
+
'bsso.none_yours': 'Aún no hay ninguna identidad vinculada.',
|
|
1853
|
+
'bsso.link': 'Vincular {provider}',
|
|
1854
|
+
'bsso.linked_now': '{provider} está vinculado: puede iniciar sesión con él.',
|
|
1855
|
+
'bsso.directories_note': 'Un directorio LDAP se vincula la primera vez que inicia sesión con él (un administrador lo permite por directorio).',
|
|
1856
|
+
'bsso.break_glass': 'de emergencia',
|
|
1857
|
+
'bsso.break_glass_help': 'Puede iniciar sesión con la contraseña de pgkiln donde un espacio de trabajo exige el inicio de sesión único, por si falla el proveedor de identidad.',
|
|
1858
|
+
'bsso.break_glass_make': 'Dar acceso de emergencia',
|
|
1859
|
+
'bsso.break_glass_remove': 'Quitar acceso de emergencia',
|
|
1860
|
+
'bsso.break_glass_on': '{user} es administrador de emergencia. Configúrele también un segundo factor.',
|
|
1861
|
+
'bsso.break_glass_off': '{user} ya no es administrador de emergencia.',
|
|
1862
|
+
'bsso.break_glass_admins': 'Solo los administradores pueden tener acceso de emergencia.',
|
|
1863
|
+
'bsso.keep_break_glass': 'Un espacio de trabajo exige el inicio de sesión único: mantenga al menos un administrador de emergencia.',
|
|
1864
|
+
'wsp.sso': 'Inicio de sesión único',
|
|
1865
|
+
'wsp.sso_only': 'Los desarrolladores de este espacio de trabajo deben iniciar sesión mediante un proveedor de identidad',
|
|
1866
|
+
'wsp.sso_only_help': 'O un directorio LDAP. Se rechazan la contraseña de pgkiln y una llave de acceso sola, salvo para los administradores de emergencia. `pgkiln admin builder-sign-in password` lo desactiva.',
|
|
1867
|
+
'wsp.sso_unlinked': 'Aún sin identidad vinculada (no pueden iniciar sesión): {list}.',
|
|
1868
|
+
'wsp.sso_save': 'Guardar la regla de inicio de sesión único',
|
|
1869
|
+
'wsp.sso_on': 'Los desarrolladores de este espacio de trabajo deben usar el inicio de sesión único.',
|
|
1870
|
+
'wsp.sso_off': 'Los desarrolladores del espacio de trabajo pueden volver a usar la contraseña de pgkiln.',
|
|
1871
|
+
'wsp.sso_no_break_glass': 'Primero dé acceso de emergencia a un administrador (Desarrolladores), para que alguien pueda entrar si falla el proveedor de identidad.',
|
|
1872
|
+
'wsp.sso_link_first': 'Vincule primero una identidad en su página de seguridad, o usted mismo no podría iniciar sesión.',
|
|
1823
1873
|
};
|
package/src/builder/i18n/fr.ts
CHANGED
|
@@ -1820,4 +1820,54 @@ export const fr: BuilderMessages = {
|
|
|
1820
1820
|
'wsp.mfa_save': 'Enregistrer la règle à deux facteurs',
|
|
1821
1821
|
'wsp.mfa_on': 'Les développeurs de cet espace de travail ont besoin de la connexion à deux facteurs.',
|
|
1822
1822
|
'wsp.mfa_off': 'L’espace de travail n’exige plus la connexion à deux facteurs (la politique de l’instance s’applique toujours).',
|
|
1823
|
+
'signin.method': 'Se connecter avec',
|
|
1824
|
+
'signin.method_pgkiln': 'Compte pgkiln',
|
|
1825
|
+
'signin.with': 'Se connecter avec {provider}',
|
|
1826
|
+
'signin.ldap_unavailable': '{directory} est injoignable. Réessayez plus tard.',
|
|
1827
|
+
'signin.sso_required': 'Votre espace de travail exige l’authentification unique : utilisez votre fournisseur d’identité.',
|
|
1828
|
+
'signin.sso_unavailable': '{provider} est injoignable. Réessayez plus tard.',
|
|
1829
|
+
'bsso.title': 'Connexion au builder',
|
|
1830
|
+
'bsso.intro': 'Les développeurs peuvent aussi se connecter au builder avec ce fournisseur. Ils sont reconnus par l’identité liée, jamais par le seul nom.',
|
|
1831
|
+
'bsso.on': 'Les développeurs peuvent s’en servir pour se connecter au builder',
|
|
1832
|
+
'bsso.on_help': 'La page de connexion l’affiche. Cette connexion se passe du second facteur de pgkiln : le fournisseur décide.',
|
|
1833
|
+
'bsso.auto_create': 'Créer les développeurs à leur première connexion',
|
|
1834
|
+
'bsso.auto_create_help': 'Pour les membres du groupe de développeurs ci-dessous (tout le monde s’il est vide). Les nouveaux développeurs rejoignent l’espace de travail Default.',
|
|
1835
|
+
'bsso.link_existing': 'Lier un développeur existant ayant le même nom d’utilisateur',
|
|
1836
|
+
'bsso.link_existing_help': 'Une fois, à la première connexion ; jamais un administrateur de secours. À activer seulement si vous faites confiance aux noms d’utilisateur du fournisseur. Sinon, les développeurs lient l’identité sur leur page Sécurité.',
|
|
1837
|
+
'bsso.group': 'Groupe des développeurs',
|
|
1838
|
+
'bsso.group_help': 'Seuls ses membres reçoivent un compte de développeur. Vide : toute personne que le fournisseur connecte.',
|
|
1839
|
+
'bsso.admin_group': 'Groupe des administrateurs',
|
|
1840
|
+
'bsso.admin_group_help': 'Si renseigné, ses membres sont administrateurs de l’instance et les autres non, vérifié à chaque connexion (les administrateurs de secours gardent leur rôle).',
|
|
1841
|
+
'bsso.save': 'Enregistrer la connexion au builder',
|
|
1842
|
+
'bsso.saved': 'Connexion au builder enregistrée.',
|
|
1843
|
+
'bsso.linked': 'Développeurs liés',
|
|
1844
|
+
'bsso.none_linked': 'Aucun développeur n’est encore lié.',
|
|
1845
|
+
'bsso.unlink': 'Délier',
|
|
1846
|
+
'bsso.unlink_confirm': 'Délier {user} ? La personne se connecte avec le mot de passe ou lie à nouveau.',
|
|
1847
|
+
'bsso.unlink_mine_confirm': 'Délier {provider} ? Vous pourrez le lier à nouveau.',
|
|
1848
|
+
'bsso.unlinked': 'L’identité est déliée.',
|
|
1849
|
+
'bsso.keep_one': 'Un espace de travail exige l’authentification unique de ce développeur : gardez une identité liée.',
|
|
1850
|
+
'bsso.yours': 'Authentification unique',
|
|
1851
|
+
'bsso.yours_intro': 'Les fournisseurs d’identité avec lesquels vous pouvez vous connecter au builder.',
|
|
1852
|
+
'bsso.none_yours': 'Aucune identité liée pour l’instant.',
|
|
1853
|
+
'bsso.link': 'Lier {provider}',
|
|
1854
|
+
'bsso.linked_now': '{provider} est lié : vous pouvez vous connecter avec.',
|
|
1855
|
+
'bsso.directories_note': 'Un annuaire LDAP est lié à votre première connexion avec lui (un administrateur l’autorise par annuaire).',
|
|
1856
|
+
'bsso.break_glass': 'de secours',
|
|
1857
|
+
'bsso.break_glass_help': 'Peut se connecter avec le mot de passe pgkiln là où un espace de travail exige l’authentification unique, au cas où le fournisseur d’identité tombe en panne.',
|
|
1858
|
+
'bsso.break_glass_make': 'Donner l’accès de secours',
|
|
1859
|
+
'bsso.break_glass_remove': 'Retirer l’accès de secours',
|
|
1860
|
+
'bsso.break_glass_on': '{user} est administrateur de secours. Donnez-lui aussi un second facteur.',
|
|
1861
|
+
'bsso.break_glass_off': '{user} n’est plus administrateur de secours.',
|
|
1862
|
+
'bsso.break_glass_admins': 'Seuls les administrateurs peuvent avoir l’accès de secours.',
|
|
1863
|
+
'bsso.keep_break_glass': 'Un espace de travail exige l’authentification unique : gardez au moins un administrateur de secours.',
|
|
1864
|
+
'wsp.sso': 'Authentification unique',
|
|
1865
|
+
'wsp.sso_only': 'Les développeurs de cet espace de travail doivent se connecter via un fournisseur d’identité',
|
|
1866
|
+
'wsp.sso_only_help': 'Ou un annuaire LDAP. Le mot de passe pgkiln et une clé d’accès seule sont refusés, sauf pour les administrateurs de secours. `pgkiln admin builder-sign-in password` le désactive.',
|
|
1867
|
+
'wsp.sso_unlinked': 'Pas encore d’identité liée (ne peuvent pas se connecter) : {list}.',
|
|
1868
|
+
'wsp.sso_save': 'Enregistrer la règle d’authentification unique',
|
|
1869
|
+
'wsp.sso_on': 'Les développeurs de cet espace de travail doivent utiliser l’authentification unique.',
|
|
1870
|
+
'wsp.sso_off': 'Les développeurs de l’espace de travail peuvent de nouveau utiliser le mot de passe pgkiln.',
|
|
1871
|
+
'wsp.sso_no_break_glass': 'Donnez d’abord l’accès de secours à un administrateur (Développeurs), pour que quelqu’un puisse se connecter si le fournisseur d’identité tombe en panne.',
|
|
1872
|
+
'wsp.sso_link_first': 'Liez d’abord une identité sur votre page Sécurité, sinon vous ne pourriez plus vous connecter.',
|
|
1823
1873
|
};
|