b20047b3eb
* 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>
34 lines
1.1 KiB
TypeScript
34 lines
1.1 KiB
TypeScript
import type { StateCreator } from "zustand";
|
|
import type { AuthSlice, AuthSliceState } from "./types";
|
|
|
|
export const defaultAuthState: AuthSliceState = {
|
|
// IMPORTANT: authenticated defaults to true (with mode "disabled") so that
|
|
// deployments/tests whose boot payload has no auth block at all (older
|
|
// backend, or a test harness that never sets initialState.auth) preserve
|
|
// today's behavior: the app shell renders immediately, no login gate.
|
|
auth: {
|
|
mode: "disabled",
|
|
authenticated: true,
|
|
user: null,
|
|
ssoProviders: [],
|
|
},
|
|
};
|
|
|
|
type ImmerSet = Parameters<StateCreator<AuthSlice, [["zustand/immer", never]], [], AuthSlice>>[0];
|
|
|
|
// The auth slice only needs `set` (no cross-slice reads), so it is typed as a
|
|
// plain single-arg creator rather than the full 3-arg StateCreator. `set`
|
|
// keeps the immer-enabled recipe typing via ImmerSet.
|
|
export const createAuthSlice = (set: ImmerSet): AuthSlice => ({
|
|
...defaultAuthState,
|
|
setAuthState: (state) =>
|
|
set((draft) => {
|
|
draft.auth = state;
|
|
}),
|
|
clearAuthenticated: () =>
|
|
set((draft) => {
|
|
draft.auth.authenticated = false;
|
|
draft.auth.user = null;
|
|
}),
|
|
});
|