feat(plugins): add auth capability for plugin-provided OIDC/SAML login (#1964)
* feat(plugins): add auth capability for plugin-provided OIDC/SAML login Plugins can now authenticate a visitor against an external IdP (OIDC/SAML) and contribute a login button to the pre-auth screen: - new `auth` manifest capability + a capability-gated X-Kandev-Auth-Login webhook-response directive; the host mints the kandev_session (ADR 0050), the plugin never receives the raw token. Plugin-set Set-Cookie is dropped. - auth.Service.AuthenticateExternal: existing-identity / link-by-email / JIT member provisioning; refuses to auto-link to an admin; verified-email is the plugin's contract. - auth_providers manifest block + plugins.SSOProviders() populate the anonymous boot payload (auth.ssoProviders); the login page renders a "Continue with <provider>" button per provider. - ADR 0050 + plugin-manifest / plugin-authoring / auth spec docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: exclude publish_review_findings_kandev from public-docs coverage The native code-review agent (#1957) added the publish_review_findings_kandev MCP tool but did not update docs/public/coverage.json; that PR did not touch docs/public so the path-filtered validator never ran. It's an internal review reporting tool, not a user-facing feature, so record it as an exclusion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(plugins,auth): address review on SSO directive and JIT provisioning - takeAuthLoginDirective strips every case variant of X-Kandev-Auth-Login and rejects (403) when a plugin sends more than one, so no variant leaks to the browser and login never uses a nondeterministically-picked assertion. - provisionExternalUser rolls back the just-created user when the identity insert fails (transient error or a lost UNIQUE(provider,subject) race) and resolves to the winning identity, so no account is left without a login and no email is reserved by an unusable row. Adds userstore.DeleteUser. - auth spec: mark the login-page SSO buttons + provider discovery as landed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: drop redundant coverage exclusion; main now covers publish_review_findings_kandev Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -253,6 +253,22 @@ its resource in `capabilities.api_read` (e.g. `tasks`, `sessions`, `messages`,
|
||||
Calling one without the declared capability returns gRPC `PermissionDenied`
|
||||
with a message naming the missing capability — declare what you use.
|
||||
|
||||
**External login (`capabilities.auth`).** An auth-capable plugin can log a
|
||||
visitor in against an external IdP (OIDC/SAML). Handle the IdP callback / SAML
|
||||
ACS in a `webhook`, validate the token yourself, then set the reserved
|
||||
`X-Kandev-Auth-Login` response header to a JSON object
|
||||
`{"provider","subject","email","display_name"}`. Kandev maps it to a user
|
||||
(link-by-email or just-in-time member provisioning), mints the session, and sets
|
||||
the `kandev_session` cookie itself — your plugin never handles the raw token,
|
||||
and any `Set-Cookie` you return is dropped. Requires authentication enabled;
|
||||
emitting the header without `capabilities.auth` returns 403.
|
||||
|
||||
**You MUST only assert an `email` the IdP has verified as owned by `subject`.**
|
||||
Kandev auto-links that email to (or provisions) an account, so an unverified or
|
||||
user-settable email claim is an account-takeover vector. Kandev refuses to
|
||||
auto-link to an admin account as defense-in-depth, but it cannot tell a verified
|
||||
email from an unverified one — that is on your plugin. See ADR 0050.
|
||||
|
||||
**Writable data directory.** Kandev injects `KANDEV_PLUGIN_DATA_DIR` into
|
||||
every spawned plugin subprocess — a per-plugin writable directory
|
||||
(`~/.kandev/plugins/<id>/data`) for anything you'd rather keep on disk than
|
||||
|
||||
Reference in New Issue
Block a user