pgkiln 0.62.0 → 0.64.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.
Files changed (61) hide show
  1. package/SECURITY.md +4 -0
  2. package/db/migrations/131_builder_sso.sql +69 -0
  3. package/db/migrations/132_app_user_mfa.sql +58 -0
  4. package/docs/apex-feature-parity.md +5 -2
  5. package/docs/guide/03-builder.md +46 -0
  6. package/docs/guide/08-security.md +48 -6
  7. package/docs/guide/09-reference.md +5 -1
  8. package/docs/guide/11-from-apex.md +2 -1
  9. package/docs/guide/12-development.md +9 -0
  10. package/docs/guide/18-cli.md +1 -0
  11. package/package.json +1 -1
  12. package/public/app.css +10 -0
  13. package/public/builder.css +4 -0
  14. package/src/account-mfa.ts +93 -0
  15. package/src/builder/advisor.ts +8 -0
  16. package/src/builder/builder-sso.ts +135 -0
  17. package/src/builder/i18n/de.ts +56 -0
  18. package/src/builder/i18n/en.ts +56 -0
  19. package/src/builder/i18n/es.ts +56 -0
  20. package/src/builder/i18n/fr.ts +56 -0
  21. package/src/builder/i18n/nl.ts +56 -0
  22. package/src/builder/i18n/pt.ts +56 -0
  23. package/src/builder/ldap.ts +2 -0
  24. package/src/builder/login-page.ts +26 -2
  25. package/src/builder/routes.ts +137 -21
  26. package/src/builder/security.ts +3 -1
  27. package/src/builder/ui.ts +2 -1
  28. package/src/builder/users.ts +36 -3
  29. package/src/builder/workspaces.ts +37 -3
  30. package/src/cli/main.ts +19 -5
  31. package/src/devsso.ts +149 -0
  32. package/src/i18n/ar.ts +34 -0
  33. package/src/i18n/cs.ts +34 -0
  34. package/src/i18n/da.ts +34 -0
  35. package/src/i18n/de.ts +34 -0
  36. package/src/i18n/el.ts +34 -0
  37. package/src/i18n/es.ts +34 -0
  38. package/src/i18n/fi.ts +34 -0
  39. package/src/i18n/fr.ts +34 -0
  40. package/src/i18n/he.ts +34 -0
  41. package/src/i18n/it.ts +34 -0
  42. package/src/i18n/ja.ts +34 -0
  43. package/src/i18n/ko.ts +34 -0
  44. package/src/i18n/nb.ts +34 -0
  45. package/src/i18n/pl.ts +34 -0
  46. package/src/i18n/pt.ts +34 -0
  47. package/src/i18n/ru.ts +34 -0
  48. package/src/i18n/sv.ts +34 -0
  49. package/src/i18n/tr.ts +34 -0
  50. package/src/i18n/uk.ts +34 -0
  51. package/src/i18n/zh.ts +34 -0
  52. package/src/i18n.ts +68 -0
  53. package/src/metadata.ts +4 -1
  54. package/src/mfa.ts +76 -52
  55. package/src/remember.ts +1 -1
  56. package/src/runtime/account.ts +4 -1
  57. package/src/runtime/routes.ts +24 -9
  58. package/src/runtime/two-step.ts +395 -0
  59. package/src/saml.ts +13 -7
  60. package/src/sharedlogin.ts +1 -1
  61. package/src/sso.ts +37 -7
package/src/sso.ts CHANGED
@@ -34,6 +34,12 @@ export interface Provider {
34
34
  protocol: 'oidc' | 'saml';
35
35
  idp_sso_url: string | null;
36
36
  idp_cert: string | null;
37
+ /** (131) developers sign in to the builder with this provider too (devsso.ts) */
38
+ builder: boolean;
39
+ builder_auto_create: boolean;
40
+ builder_link_existing: boolean;
41
+ builder_group: string | null;
42
+ builder_admin_group: string | null;
37
43
  }
38
44
 
39
45
  interface Discovery {
@@ -86,8 +92,11 @@ function jwks(doc: Discovery) {
86
92
  return set;
87
93
  }
88
94
 
89
- /** Start: returns the provider URL to redirect to, and the browser-binding cookie value. */
90
- export async function startSignIn(p: Provider, appId: number, next: string | undefined) {
95
+ /**
96
+ * Start: returns the provider URL to redirect to, and the browser-binding cookie value.
97
+ * appId null: a sign-in to the builder; link: the signed-in developer linking this identity.
98
+ */
99
+ export async function startSignIn(p: Provider, appId: number | null, next: string | undefined, link?: string) {
91
100
  const doc = await discover(p.issuer);
92
101
  const state = b64url(randomBytes(32));
93
102
  const nonce = b64url(randomBytes(32));
@@ -95,9 +104,9 @@ export async function startSignIn(p: Provider, appId: number, next: string | und
95
104
  const browserKey = b64url(randomBytes(32));
96
105
  await owner.query(`delete from meta.sso_pending where created_at < now() - make_interval(mins => $1)`, [PENDING_MINUTES]);
97
106
  await owner.query(
98
- `insert into meta.sso_pending (state, app_id, provider_id, code_verifier, nonce, browser_hash, next)
99
- values ($1, $2, $3, $4, $5, $6, $7)`,
100
- [state, appId, p.id, verifier, nonce, sha256(browserKey), next ?? null],
107
+ `insert into meta.sso_pending (state, app_id, provider_id, code_verifier, nonce, browser_hash, next, link_username)
108
+ values ($1, $2, $3, $4, $5, $6, $7, $8)`,
109
+ [state, appId, p.id, verifier, nonce, sha256(browserKey), next ?? null, link ?? null],
101
110
  );
102
111
  const url = new URL(doc.authorization_endpoint);
103
112
  url.search = new URLSearchParams({
@@ -113,16 +122,29 @@ export async function startSignIn(p: Provider, appId: number, next: string | und
113
122
  return { url: url.toString(), browserKey };
114
123
  }
115
124
 
125
+ // (sprint 129) the builder finishes its own sign-ins (app_id null); the callback routes are the runtime's
126
+ type BuilderFinish = (req: any, reply: any, p: Provider, result: SsoResult, ip: string) => Promise<unknown>;
127
+ let builderFinish: BuilderFinish | null = null;
128
+ export const onBuilderSso = (f: BuilderFinish) => {
129
+ builderFinish = f;
130
+ };
131
+ export const builderSsoFinish = () => builderFinish;
132
+
116
133
  /** Read a claim by name or dot path (e.g. realm_access.roles). */
117
134
  function claim(payload: JWTPayload, path: string): unknown {
118
135
  return path.split('.').reduce<any>((v, k) => (v && typeof v === 'object' ? v[k] : undefined), payload);
119
136
  }
120
137
 
121
138
  export interface SsoResult {
122
- appId: number;
139
+ /** null: a sign-in to the builder (131); username is then the provider's, not resolved to an account */
140
+ appId: number | null;
123
141
  next: string | null;
124
142
  username: string;
125
143
  groups: string[];
144
+ /** builder sign-ins: the identity, for devsso.ts */
145
+ identity?: { subject: string; name: string | null; email: string | null; mayLink: boolean };
146
+ /** builder: the signed-in developer who links this identity (Security page) */
147
+ link?: string | null;
126
148
  }
127
149
 
128
150
  /** Callback: validate state/browser, exchange the code, verify the ID token, resolve the account. */
@@ -132,7 +154,7 @@ export async function finishSignIn(p: Provider, params: URLSearchParams, browser
132
154
  const code = params.get('code') ?? '';
133
155
  const pending = await owner.one(
134
156
  `delete from meta.sso_pending where state = $1 and provider_id = $2 and created_at > now() - make_interval(mins => $3)
135
- returning app_id, code_verifier, nonce, browser_hash, next`,
157
+ returning app_id, code_verifier, nonce, browser_hash, next, link_username`,
136
158
  [state, p.id, PENDING_MINUTES],
137
159
  );
138
160
  if (!pending) throw new SsoError('This sign-in link has expired or was already used. Please try again.');
@@ -181,6 +203,14 @@ export async function finishSignIn(p: Provider, params: URLSearchParams, browser
181
203
 
182
204
  // an unverified e-mail address never links an existing account
183
205
  const verified = p.username_claim !== 'email' || payload.email_verified === true;
206
+ if (pending.app_id === null) {
207
+ // the builder: devsso.ts finds the developer (sprint 129)
208
+ const email = payload.email_verified === false ? null : typeof payload.email === 'string' ? payload.email : null;
209
+ return {
210
+ appId: null, next: pending.next, username, groups, link: pending.link_username,
211
+ identity: { subject: payload.sub, name: typeof payload.name === 'string' ? payload.name : null, email, mayLink: verified },
212
+ };
213
+ }
184
214
  const account = await resolveAccount(p, payload, username, { mayLink: verified });
185
215
  return { appId: pending.app_id, next: pending.next, username: account, groups };
186
216
  }